"Fossies" - the Fresh Open Source Software Archive

Member "rawtherapee-5.7/rtengine/iccstore.cc" (10 Sep 2019, 40280 Bytes) of package /linux/misc/rawtherapee-5.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "iccstore.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6_vs_5.7.

    1 /*
    2  *  This file is part of RawTherapee.
    3  *
    4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
    5  *
    6  *  RawTherapee is free software: you can redistribute it and/or modify
    7  *  it under the terms of the GNU General Public License as published by
    8  *  the Free Software Foundation, either version 3 of the License, or
    9  *  (at your option) any later version.
   10  *
   11  *  RawTherapee is distributed in the hope that it will be useful,
   12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  *  GNU General Public License for more details.
   15  *
   16  *  You should have received a copy of the GNU General Public License
   17  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
   18  */
   19 #include <cstring>
   20 
   21 #include <glibmm.h>
   22 #include <glib/gstdio.h>
   23 
   24 #ifdef WIN32
   25 #include <winsock2.h>
   26 #else
   27 #include <netinet/in.h>
   28 #endif
   29 
   30 #include <iostream>
   31 
   32 #include "iccstore.h"
   33 
   34 #include "iccmatrices.h"
   35 
   36 #include "../rtgui/options.h"
   37 #include "../rtgui/threadutils.h"
   38 #include "lcms2_plugin.h"
   39 
   40 #include "color.h"
   41 
   42 #include "cJSON.h"
   43 #define inkc_constant 0x696E6B43
   44 namespace rtengine
   45 {
   46 
   47 extern const Settings* settings;
   48 
   49 }
   50 
   51 namespace
   52 {
   53 
   54 // Not recursive
   55 void loadProfiles(
   56     const Glib::ustring& dirName,
   57     std::map<Glib::ustring, cmsHPROFILE>* profiles,
   58     std::map<Glib::ustring, rtengine::ProfileContent>* profileContents,
   59     std::map<Glib::ustring, Glib::ustring>* profileNames,
   60     bool nameUpper
   61 )
   62 {
   63     if (dirName.empty()) {
   64         return;
   65     }
   66 
   67     try {
   68         Glib::Dir dir(dirName);
   69 
   70         for (Glib::DirIterator entry = dir.begin(); entry != dir.end(); ++entry) {
   71             const Glib::ustring fileName = *entry;
   72 
   73             if (fileName.size() < 4) {
   74                 continue;
   75             }
   76 
   77             const Glib::ustring extension = rtengine::getFileExtension(fileName);
   78 
   79             if (extension != "icc" && extension != "icm") {
   80                 continue;
   81             }
   82 
   83             const Glib::ustring filePath = Glib::build_filename(dirName, fileName);
   84 
   85             if (!Glib::file_test(filePath, Glib::FILE_TEST_IS_REGULAR)) {
   86                 continue;
   87             }
   88 
   89             Glib::ustring name = fileName.substr(0, fileName.size() - 4);
   90 
   91             if (nameUpper) {
   92                 name = name.uppercase();
   93             }
   94 
   95             if (profiles) {
   96                 const rtengine::ProfileContent content(filePath);
   97                 const cmsHPROFILE profile = content.toProfile();
   98 
   99                 if (profile) {
  100                     profiles->emplace(name, profile);
  101 
  102                     if (profileContents) {
  103                         profileContents->emplace(name, content);
  104                     }
  105                 }
  106             }
  107 
  108             if (profileNames) {
  109                 profileNames->emplace(name, filePath);
  110             }
  111         }
  112     } catch (Glib::Exception&) {
  113     }
  114 }
  115 
  116 // Version dedicated to single profile load when loadAll==false (cli version "-q" mode)
  117 bool loadProfile(
  118     const Glib::ustring& profile,
  119     const Glib::ustring& dirName,
  120     std::map<Glib::ustring, cmsHPROFILE>* profiles,
  121     std::map<Glib::ustring, rtengine::ProfileContent>* profileContents
  122 )
  123 {
  124     if (dirName.empty() || profiles == nullptr) {
  125         return false;
  126     }
  127 
  128     try {
  129         Glib::Dir dir(dirName);
  130 
  131         for (Glib::DirIterator entry = dir.begin(); entry != dir.end(); ++entry) {
  132             const Glib::ustring fileName = *entry;
  133 
  134             if (fileName.size() < 4) {
  135                 continue;
  136             }
  137 
  138             const Glib::ustring extension = rtengine::getFileExtension(fileName);
  139 
  140             if (extension != "icc" && extension != "icm") {
  141                 continue;
  142             }
  143 
  144             const Glib::ustring filePath = Glib::build_filename(dirName, fileName);
  145 
  146             if (!Glib::file_test(filePath, Glib::FILE_TEST_IS_REGULAR)) {
  147                 continue;
  148             }
  149 
  150             const Glib::ustring name = fileName.substr(0, fileName.size() - 4);
  151 
  152             if (name == profile) {
  153                 const rtengine::ProfileContent content(filePath);
  154                 const cmsHPROFILE profile = content.toProfile();
  155 
  156                 if (profile) {
  157                     profiles->emplace(name, profile);
  158 
  159                     if (profileContents) {
  160                         profileContents->emplace(name, content);
  161                     }
  162 
  163                     return true;
  164                 }
  165             }
  166         }
  167     } catch (Glib::Exception&) {
  168     }
  169 
  170     return false;
  171 }
  172 
  173 void getSupportedIntent(cmsHPROFILE profile, cmsUInt32Number intent, cmsUInt32Number direction, uint8_t& result)
  174 {
  175     if (cmsIsIntentSupported(profile, intent, direction)) {
  176         result |= 1 << intent;
  177     }
  178 }
  179 
  180 uint8_t getSupportedIntents(cmsHPROFILE profile, cmsUInt32Number direction)
  181 {
  182     if (!profile) {
  183         return 0;
  184     }
  185 
  186     uint8_t result = 0;
  187 
  188     getSupportedIntent(profile, INTENT_PERCEPTUAL, direction, result);
  189     getSupportedIntent(profile, INTENT_RELATIVE_COLORIMETRIC, direction, result);
  190     getSupportedIntent(profile, INTENT_SATURATION, direction, result);
  191     getSupportedIntent(profile, INTENT_ABSOLUTE_COLORIMETRIC, direction, result);
  192 
  193     return result;
  194 }
  195 
  196 cmsHPROFILE createXYZProfile()
  197 {
  198     double mat[3][3] = { {1.0, 0, 0}, {0, 1.0, 0}, {0, 0, 1.0} };
  199     return rtengine::ICCStore::createFromMatrix(mat, false, "XYZ");
  200 }
  201 
  202 const double(*wprofiles[])[3]  = {xyz_sRGB, xyz_adobe, xyz_prophoto, xyz_widegamut, xyz_bruce, xyz_beta, xyz_best, xyz_rec2020, xyz_ACESp0, xyz_ACESp1};//
  203 const double(*iwprofiles[])[3] = {sRGB_xyz, adobe_xyz, prophoto_xyz, widegamut_xyz, bruce_xyz, beta_xyz, best_xyz, rec2020_xyz, ACESp0_xyz, ACESp1_xyz};//
  204 const char* wpnames[] = {"sRGB", "Adobe RGB", "ProPhoto", "WideGamut", "BruceRGB", "Beta RGB", "BestRGB", "Rec2020", "ACESp0", "ACESp1"};//
  205 //default = gamma inside profile
  206 //BT709 g=2.22 s=4.5  sRGB g=2.4 s=12.92310
  207 //linear g=1.0
  208 //std22 g=2.2   std18 g=1.8
  209 // high  g=1.3 s=3.35  for high dynamic images
  210 //low  g=2.6 s=6.9  for low contrast images
  211 
  212 //-----------------------------------------------------------------------------
  213 // helper functions to fix V2 profiles TRCs, used in
  214 // rtengine::ProfileContent::toProfile()
  215 // see https://github.com/Beep6581/RawTherapee/issues/5026
  216 // -----------------------------------------------------------------------------
  217 bool is_RTv2_profile(cmsHPROFILE profile)
  218 {
  219     if (int(cmsGetProfileVersion(profile)) != 2) {
  220         return false;
  221     }
  222     const cmsMLU *mlu = static_cast<const cmsMLU *>(cmsReadTag(profile, cmsSigDeviceMfgDescTag));
  223     if (!mlu) {
  224         return false;
  225     }
  226     cmsUInt32Number sz = cmsMLUgetASCII(mlu, "en", "US", nullptr, 0);
  227     if (!sz) {
  228         return false;
  229     }
  230     std::vector<char> buf(sz);
  231     cmsMLUgetASCII(mlu, "en", "US", &buf[0], sz);
  232     buf.back() = 0; // sanity
  233     return strcmp(&buf[0], "RawTherapee") == 0;
  234 }
  235 
  236 
  237 bool get_RT_gamma_slope(cmsHPROFILE profile, double &gammatag, double &slopetag)
  238 {
  239     const cmsMLU *modelDescMLU = static_cast<const cmsMLU *>(cmsReadTag(profile, cmsSigDeviceModelDescTag));
  240     if (modelDescMLU) {
  241         cmsUInt32Number count = cmsMLUgetWide(modelDescMLU, "en", "US", nullptr, 0);
  242         if (count) {
  243             std::vector<wchar_t> vbuf(count);
  244             wchar_t *buffer = &vbuf[0];
  245             count = cmsMLUgetWide(modelDescMLU, "en", "US", buffer, count);
  246             Glib::ustring modelDesc;
  247 #if __SIZEOF_WCHAR_T__ == 2
  248             char *cModelDesc = g_utf16_to_utf8((unsigned short int*)buffer, -1, nullptr, nullptr, nullptr); // convert to utf-8 in a buffer allocated by glib
  249             if (cModelDesc) {
  250                 modelDesc.assign(cModelDesc);
  251                 g_free(cModelDesc);
  252             }
  253 #else
  254             modelDesc = utf32_to_utf8(buffer, count);
  255 #endif
  256             if (!modelDesc.empty()) {
  257                 try {
  258                     std::size_t pos = modelDesc.find("g");
  259                     std::size_t posmid = modelDesc.find("s");
  260                     std::size_t posend = modelDesc.find("!");
  261                     if (pos == std::string::npos || posmid == std::string::npos || posend == std::string::npos) {
  262                         return false;
  263                     }
  264                     std::string strgamma = modelDesc.substr(pos + 1, (posmid - pos));
  265                     gammatag = std::stod(strgamma.c_str());
  266                     std::string strslope = modelDesc.substr(posmid + 1, (posend - posmid));
  267                     slopetag = std::stod(strslope.c_str());
  268                     return true;
  269                 } catch (std::invalid_argument &) {
  270                     return false;
  271                 }
  272             }
  273         }
  274     }
  275     return false;
  276 }
  277 
  278 
  279 Glib::ustring get_profile_description(cmsHPROFILE profile)
  280 {
  281     const cmsMLU *mlu = static_cast<const cmsMLU *>(cmsReadTag(profile, cmsSigProfileDescriptionTag));
  282     if (!mlu) {
  283         return "";
  284     }
  285     cmsUInt32Number sz = cmsMLUgetASCII(mlu, "en", "US", nullptr, 0);
  286     if (!sz) {
  287         return "";
  288     }
  289     std::vector<char> buf(sz);
  290     cmsMLUgetASCII(mlu, "en", "US", &buf[0], sz);
  291     buf.back() = 0; // sanity
  292     return std::string(&buf[0]);
  293 }
  294 
  295 } // namespace
  296 
  297 
  298 rtengine::ProfileContent::ProfileContent() = default;
  299 
  300 rtengine::ProfileContent::ProfileContent(const Glib::ustring& fileName)
  301 {
  302     FILE* const f = g_fopen(fileName.c_str(), "rb");
  303 
  304     if (!f) {
  305         return;
  306     }
  307 
  308     fseek(f, 0, SEEK_END);
  309     long length = ftell(f);
  310 
  311     if (length > 0) {
  312         char* d = new char[length + 1];
  313         fseek(f, 0, SEEK_SET);
  314         length = fread(d, 1, length, f);
  315         d[length] = 0;
  316         data.assign(d, length);
  317         delete[] d;
  318     } else {
  319         data.clear();
  320     }
  321 
  322     fclose(f);
  323 
  324 }
  325 
  326 rtengine::ProfileContent::ProfileContent(cmsHPROFILE hProfile)
  327 {
  328     if (hProfile != nullptr) {
  329         cmsUInt32Number bytesNeeded = 0;
  330         cmsSaveProfileToMem(hProfile, nullptr, &bytesNeeded);
  331 
  332         if (bytesNeeded > 0) {
  333             char* d = new char[bytesNeeded + 1];
  334             cmsSaveProfileToMem(hProfile, d, &bytesNeeded);
  335             data.assign(d, bytesNeeded);
  336             delete[] d;
  337         }
  338     }
  339 }
  340 
  341 cmsHPROFILE rtengine::ProfileContent::toProfile() const
  342 {
  343     cmsHPROFILE profile = nullptr;
  344     if (!data.empty()) {
  345         profile = cmsOpenProfileFromMem(data.c_str(), data.size());
  346         // if this is a V2 profile generated by RawTherapee, we rebuild the
  347         // TRC. See https://github.com/Beep6581/RawTherapee/issues/5026 and
  348         // the references in there
  349         if (profile && is_RTv2_profile(profile)) {
  350             double gammatag, slopetag;
  351             if (get_RT_gamma_slope(profile, gammatag, slopetag)) {
  352                 constexpr double eps = 0.000000001; // not divide by zero
  353                 double pwr = 1.0 / gammatag;
  354                 double ts = slopetag;
  355                 double slope = slopetag == 0 ? eps : slopetag;
  356 
  357                 GammaValues g_b; //gamma parameters
  358                 Color::calcGamma(pwr, ts, 0, g_b); // call to calcGamma with selected gamma and slope : return parameters for LCMS2
  359                 cmsFloat64Number gammaParams[7]; //gamma parameters
  360                 gammaParams[4] = g_b[3] * ts;
  361                 gammaParams[0] = gammatag;
  362                 gammaParams[1] = 1. / (1.0 + g_b[4]);
  363                 gammaParams[2] = g_b[4] / (1.0 + g_b[4]);
  364                 gammaParams[3] = 1. / slope;
  365                 gammaParams[5] = 0.0;
  366                 gammaParams[6] = 0.0;
  367 
  368                 cmsToneCurve* GammaTRC;
  369                 if (slopetag == 0.) {
  370                     //printf("gammatag=%f\n", gammatag);
  371                     GammaTRC = cmsBuildGamma(NULL, gammatag);
  372                 } else {
  373                     GammaTRC = cmsBuildParametricToneCurve(nullptr, 5, gammaParams); //5 = smoother than 4
  374                 }
  375                 cmsWriteTag(profile, cmsSigRedTRCTag, GammaTRC);
  376                 cmsWriteTag(profile, cmsSigGreenTRCTag, GammaTRC);
  377                 cmsWriteTag(profile, cmsSigBlueTRCTag, GammaTRC);
  378                 cmsFreeToneCurve(GammaTRC);
  379 
  380                 if (settings->verbose) {
  381                     std::cout << "ICCStore: rebuilt TRC for RTv2 profile " << get_profile_description(profile) << ": gamma=" << gammatag << ", slope=" << slopetag << std::endl;
  382                 }
  383             } else if (settings->verbose) {
  384                 std::cout << "ICCStore: no gamma/slope info found for RTv2 profile " << get_profile_description(profile) << std::endl;
  385             }
  386         }
  387     }
  388     return profile;
  389 }
  390 
  391 const std::string& rtengine::ProfileContent::getData() const
  392 {
  393     return data;
  394 }
  395 
  396 class rtengine::ICCStore::Implementation
  397 {
  398 public:
  399     Implementation() :
  400         loadAll(true),
  401         xyz(createXYZProfile()),
  402         srgb(cmsCreate_sRGBProfile())
  403     {
  404         //cmsErrorAction(LCMS_ERROR_SHOW);
  405 
  406         constexpr int N = sizeof(wpnames) / sizeof(wpnames[0]);
  407 
  408         for (int i = 0; i < N; ++i) {
  409             wProfiles[wpnames[i]] = createFromMatrix(wprofiles[i]);
  410             // wProfilesGamma[wpnames[i]] = createFromMatrix(wprofiles[i], true);
  411             wMatrices[wpnames[i]] = wprofiles[i];
  412             iwMatrices[wpnames[i]] = iwprofiles[i];
  413         }
  414     }
  415 
  416     ~Implementation()
  417     {
  418         for (auto &p : wProfiles) {
  419             if (p.second) {
  420                 cmsCloseProfile(p.second);
  421             }
  422         }
  423 
  424         // for (auto &p : wProfilesGamma) {
  425         //     if (p.second) {
  426         //         cmsCloseProfile(p.second);
  427         //     }
  428         // }
  429         for (auto &p : fileProfiles) {
  430             if (p.second) {
  431                 cmsCloseProfile(p.second);
  432             }
  433         }
  434 
  435         if (srgb) {
  436             cmsCloseProfile(srgb);
  437         }
  438 
  439         if (xyz) {
  440             cmsCloseProfile(xyz);
  441         }
  442     }
  443 
  444     void init(const Glib::ustring& usrICCDir, const Glib::ustring& rtICCDir, bool loadAll)
  445     {
  446         // Reads all profiles from the given profiles dir
  447 
  448         MyMutex::MyLock lock(mutex);
  449 
  450         this->loadAll = loadAll;
  451 
  452         // RawTherapee's profiles take precedence if a user's profile of the same name exists
  453         profilesDir = Glib::build_filename(rtICCDir, "output");
  454         userICCDir = usrICCDir;
  455         fileProfiles.clear();
  456         fileProfileContents.clear();
  457 
  458         if (loadAll) {
  459             loadProfiles(profilesDir, &fileProfiles, &fileProfileContents, nullptr, false);
  460             loadProfiles(userICCDir, &fileProfiles, &fileProfileContents, nullptr, false);
  461         }
  462 
  463         // Input profiles
  464         // Load these to different areas, since the short name(e.g. "NIKON D700" may overlap between system/user and RT dir)
  465         stdProfilesDir = Glib::build_filename(rtICCDir, "input");
  466         fileStdProfiles.clear();
  467         fileStdProfilesFileNames.clear();
  468 
  469         if (loadAll) {
  470             loadProfiles(stdProfilesDir, nullptr, nullptr, &fileStdProfilesFileNames, true);
  471             Glib::ustring user_input_icc_dir = Glib::build_filename(options.rtdir, "iccprofiles", "input");
  472             loadProfiles(user_input_icc_dir, nullptr, nullptr, &fileStdProfilesFileNames, true);
  473         }
  474 
  475         defaultMonitorProfile = settings->monitorProfile;
  476 
  477         loadWorkingSpaces(rtICCDir);
  478         loadWorkingSpaces(userICCDir);
  479 
  480         // initialize the alarm colours for lcms gamut checking -- we use bright green
  481         cmsUInt16Number cms_alarm_codes[cmsMAXCHANNELS] = { 0, 65535, 65535 };
  482         cmsSetAlarmCodes(cms_alarm_codes);
  483     }
  484 
  485     cmsHPROFILE workingSpace(const Glib::ustring& name) const
  486     {
  487         const ProfileMap::const_iterator r = wProfiles.find(name);
  488 
  489         return
  490             r != wProfiles.end()
  491             ? r->second
  492             : wProfiles.find("sRGB")->second;
  493     }
  494 
  495     // cmsHPROFILE workingSpaceGamma(const Glib::ustring& name) const
  496     // {
  497 
  498     //     const ProfileMap::const_iterator r = wProfilesGamma.find(name);
  499 
  500     //     return
  501     //         r != wProfilesGamma.end()
  502     //             ? r->second
  503     //             : wProfilesGamma.find("sRGB")->second;
  504     // }
  505 
  506     TMatrix workingSpaceMatrix(const Glib::ustring& name) const
  507     {
  508         const MatrixMap::const_iterator r = wMatrices.find(name);
  509 
  510         return
  511             r != wMatrices.end()
  512             ? r->second
  513             : wMatrices.find("sRGB")->second;
  514     }
  515 
  516     TMatrix workingSpaceInverseMatrix(const Glib::ustring& name) const
  517     {
  518 
  519         const MatrixMap::const_iterator r = iwMatrices.find(name);
  520 
  521         return
  522             r != iwMatrices.end()
  523             ? r->second
  524             : iwMatrices.find("sRGB")->second;
  525     }
  526 
  527     bool outputProfileExist(const Glib::ustring& name) const
  528     {
  529         MyMutex::MyLock lock(mutex);
  530         return fileProfiles.find(name) != fileProfiles.end();
  531     }
  532 
  533     cmsHPROFILE getProfile(const Glib::ustring& name)
  534     {
  535         MyMutex::MyLock lock(mutex);
  536 
  537         const ProfileMap::const_iterator r = fileProfiles.find(name);
  538 
  539         if (r != fileProfiles.end()) {
  540             return r->second;
  541         }
  542 
  543         if (!name.compare(0, 5, "file:")) {
  544             const ProfileContent content(name.substr(5));
  545             const cmsHPROFILE profile = content.toProfile();
  546 
  547             if (profile) {
  548                 fileProfiles.emplace(name, profile);
  549                 fileProfileContents.emplace(name, content);
  550 
  551                 return profile;
  552             }
  553         } else if (!loadAll) {
  554             // Look for a standard profile
  555             if (!loadProfile(name, profilesDir, &fileProfiles, &fileProfileContents)) {
  556                 loadProfile(name, userICCDir, &fileProfiles, &fileProfileContents);
  557             }
  558 
  559             const ProfileMap::const_iterator r = fileProfiles.find(name);
  560 
  561             if (r != fileProfiles.end()) {
  562                 return r->second;
  563             }
  564         }
  565 
  566         return nullptr;
  567     }
  568 
  569     cmsHPROFILE getStdProfile(const Glib::ustring& name)
  570     {
  571         const Glib::ustring nameUpper = name.uppercase();
  572 
  573         MyMutex::MyLock lock(mutex);
  574 
  575         const ProfileMap::const_iterator r = fileStdProfiles.find(nameUpper);
  576 
  577         // Return profile from store
  578         if (r != fileStdProfiles.end()) {
  579             return r->second;
  580         } else if (!loadAll) {
  581             // Directory not scanned, so looking and adding now...
  582             if (!loadProfile(name, profilesDir, &fileProfiles, &fileProfileContents)) {
  583                 loadProfile(name, userICCDir, &fileProfiles, &fileProfileContents);
  584             }
  585 
  586             const ProfileMap::const_iterator r = fileProfiles.find(name);
  587 
  588             if (r != fileProfiles.end()) {
  589                 return r->second;
  590             }
  591         }
  592 
  593         // Profile is not yet in store
  594         const NameMap::const_iterator f = fileStdProfilesFileNames.find(nameUpper);
  595 
  596         // Profile does not exist
  597         if (f == fileStdProfilesFileNames.end()) {
  598             return nullptr;
  599         }
  600 
  601         // But there exists one --> load it
  602         const ProfileContent content(f->second);
  603         const cmsHPROFILE profile = content.toProfile();
  604 
  605         if (profile) {
  606             fileStdProfiles.emplace(f->first, profile);
  607         }
  608 
  609         // Profile invalid or stored now --> remove entry from fileStdProfilesFileNames
  610         fileStdProfilesFileNames.erase(f);
  611         return profile;
  612     }
  613 
  614     ProfileContent getContent(const Glib::ustring& name) const
  615     {
  616         MyMutex::MyLock lock(mutex);
  617 
  618         const ContentMap::const_iterator r = fileProfileContents.find(name);
  619 
  620         return
  621             r != fileProfileContents.end()
  622             ? r->second
  623             : ProfileContent();
  624     }
  625 
  626     cmsHPROFILE getXYZProfile() const
  627     {
  628         return xyz;
  629     }
  630 
  631     cmsHPROFILE getsRGBProfile() const
  632     {
  633         return srgb;
  634     }
  635 
  636     std::vector<Glib::ustring> getProfiles(ProfileType type) const
  637     {
  638         std::vector<Glib::ustring> res;
  639 
  640         MyMutex::MyLock lock(mutex);
  641 
  642         for (const auto profile : fileProfiles) {
  643             if (
  644                 (
  645                     type == ICCStore::ProfileType::MONITOR
  646                     && cmsGetDeviceClass(profile.second) == cmsSigDisplayClass
  647                     && cmsGetColorSpace(profile.second) == cmsSigRgbData
  648                 )
  649                 || (
  650                     type == ICCStore::ProfileType::PRINTER
  651                     && cmsGetDeviceClass(profile.second) == cmsSigOutputClass
  652                 )
  653                 || (
  654                     type == ICCStore::ProfileType::OUTPUT
  655                     && (cmsGetDeviceClass(profile.second) == cmsSigDisplayClass
  656                         || cmsGetDeviceClass(profile.second) == cmsSigInputClass
  657                         || cmsGetDeviceClass(profile.second) == cmsSigOutputClass)
  658                     && cmsGetColorSpace(profile.second) == cmsSigRgbData
  659                 )
  660             ) {
  661                 res.push_back(profile.first);
  662             }
  663         }
  664 
  665         return res;
  666     }
  667 
  668     std::vector<Glib::ustring> getProfilesFromDir(const Glib::ustring& dirName) const
  669     {
  670         std::vector<Glib::ustring> res;
  671         ProfileMap profiles;
  672 
  673         MyMutex::MyLock lock(mutex);
  674 
  675         loadProfiles(profilesDir, &profiles, nullptr, nullptr, false);
  676         loadProfiles(dirName, &profiles, nullptr, nullptr, false);
  677 
  678         for (const auto& profile : profiles) {
  679             res.push_back(profile.first);
  680         }
  681 
  682         return res;
  683     }
  684 
  685     std::uint8_t getInputIntents(cmsHPROFILE profile)
  686     {
  687         MyMutex::MyLock lock(mutex);
  688 
  689         return getSupportedIntents(profile, LCMS_USED_AS_INPUT);
  690     }
  691 
  692     std::uint8_t getOutputIntents(cmsHPROFILE profile)
  693     {
  694         MyMutex::MyLock lock(mutex);
  695 
  696         return getSupportedIntents(profile, LCMS_USED_AS_OUTPUT);
  697     }
  698 
  699     std::uint8_t getProofIntents(cmsHPROFILE profile)
  700     {
  701         MyMutex::MyLock lock(mutex);
  702 
  703         return getSupportedIntents(profile, LCMS_USED_AS_PROOF);
  704     }
  705 
  706     std::uint8_t getInputIntents(const Glib::ustring &name)
  707     {
  708         return getInputIntents(getProfile(name));
  709     }
  710 
  711     std::uint8_t getOutputIntents(const Glib::ustring &name)
  712     {
  713         return getOutputIntents(getProfile(name));
  714     }
  715 
  716     std::uint8_t getProofIntents(const Glib::ustring &name)
  717     {
  718         return getProofIntents(getProfile(name));
  719     }
  720 
  721     Glib::ustring getDefaultMonitorProfileName() const
  722     {
  723         return defaultMonitorProfile;
  724     }
  725 
  726     void setDefaultMonitorProfileName(const Glib::ustring &name)
  727     {
  728         MyMutex::MyLock lock(mutex);
  729         defaultMonitorProfile = name;
  730     }
  731 
  732     std::vector<Glib::ustring> getWorkingProfiles()
  733     {
  734         std::vector<Glib::ustring> res;
  735 
  736         // for (unsigned int i = 0; i < sizeof(wpnames) / sizeof(wpnames[0]); i++) {
  737         //     res.push_back(wpnames[i]);
  738         // }
  739         for (const auto &p : wProfiles) {
  740             res.push_back(p.first);
  741         }
  742 
  743         return res;
  744     }
  745 
  746 private:
  747     using CVector = std::array<double, 3>;
  748     using CMatrix = std::array<CVector, 3>;
  749     struct PMatrix {
  750         double matrix[3][3];
  751         PMatrix(): matrix{} {}
  752         explicit PMatrix(const CMatrix &m)
  753         {
  754             set(m);
  755         }
  756 
  757         CMatrix toMatrix() const
  758         {
  759             CMatrix ret;
  760 
  761             for (int i = 0; i < 3; ++i) {
  762                 for (int j = 0; j < 3; ++j) {
  763                     ret[i][j] = matrix[i][j];
  764                 }
  765             }
  766 
  767             return ret;
  768         }
  769 
  770         void set(const CMatrix &m)
  771         {
  772             for (int i = 0; i < 3; ++i) {
  773                 for (int j = 0; j < 3; ++j) {
  774                     matrix[i][j] = m[i][j];
  775                 }
  776             }
  777         }
  778     };
  779 
  780     bool computeWorkingSpaceMatrix(const Glib::ustring &path, const Glib::ustring &filename, PMatrix &out)
  781     {
  782         Glib::ustring fullpath = filename;
  783 
  784         if (!Glib::path_is_absolute(fullpath)) {
  785             fullpath = Glib::build_filename(path, filename);
  786         }
  787 
  788         ProfileContent content(fullpath);
  789         cmsHPROFILE prof = content.toProfile();
  790 
  791         if (!prof) {
  792             return false;
  793         }
  794 
  795         if (cmsGetColorSpace(prof) != cmsSigRgbData) {
  796             cmsCloseProfile(prof);
  797             return false;
  798         }
  799 
  800         if (!cmsIsMatrixShaper(prof)) {
  801             cmsCloseProfile(prof);
  802             return false;
  803         }
  804 
  805         cmsCIEXYZ *red = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigRedMatrixColumnTag));
  806         cmsCIEXYZ *green  = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigGreenMatrixColumnTag));
  807         cmsCIEXYZ *blue  = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigBlueMatrixColumnTag));
  808 
  809         if (!red || !green || !blue) {
  810             cmsCloseProfile(prof);
  811             return false;
  812         }
  813 
  814         CMatrix m = {
  815             CVector({ red->X, green->X, blue->X }),
  816             CVector({ red->Y, green->Y, blue->Y }),
  817             CVector({ red->Z, green->Z, blue->Z })
  818         };
  819         m[1][0] = red->Y;
  820         m[1][1] = green->Y;
  821         m[1][2] = blue->Y;
  822         m[2][0] = red->Z;
  823         m[2][1] = green->Z;
  824         m[2][2] = blue->Z;
  825         out.set(m);
  826 
  827         cmsCloseProfile(prof);
  828         return true;
  829     }
  830 
  831     bool loadWorkingSpaces(const Glib::ustring &path)
  832     {
  833         Glib::ustring fileName = Glib::build_filename(path, "workingspaces.json");
  834         FILE* const f = g_fopen(fileName.c_str(), "r");
  835 
  836         if (settings->verbose) {
  837             std::cout << "trying to load extra working spaces from " << fileName << std::flush;
  838         }
  839 
  840         if (!f) {
  841             if (settings->verbose) {
  842                 std::cout << " FAIL" << std::endl;
  843             }
  844 
  845             return false;
  846         }
  847 
  848         fseek(f, 0, SEEK_END);
  849         long length = ftell(f);
  850 
  851         if (length <= 0) {
  852             if (settings->verbose) {
  853                 std::cout << " FAIL" << std::endl;
  854             }
  855 
  856             fclose(f);
  857             return false;
  858         }
  859 
  860         char *buf = new char[length + 1];
  861         fseek(f, 0, SEEK_SET);
  862         length = fread(buf, 1, length, f);
  863         buf[length] = 0;
  864 
  865         fclose(f);
  866 
  867         cJSON_Minify(buf);
  868         cJSON *root = cJSON_Parse(buf);
  869 
  870         if (!root) {
  871             if (settings->verbose) {
  872                 std::cout << " FAIL" << std::endl;
  873             }
  874 
  875             return false;
  876         }
  877 
  878         delete[] buf;
  879 
  880         cJSON *js = cJSON_GetObjectItem(root, "working_spaces");
  881 
  882         if (!js) {
  883             goto parse_error;
  884         }
  885 
  886         for (js = js->child; js != nullptr; js = js->next) {
  887             cJSON *ji = cJSON_GetObjectItem(js, "name");
  888             std::unique_ptr<PMatrix> m(new PMatrix);
  889             std::string name;
  890 
  891             if (!ji || ji->type != cJSON_String) {
  892                 goto parse_error;
  893             }
  894 
  895             name = ji->valuestring;
  896 
  897             if (wProfiles.find(name) != wProfiles.end()) {
  898                 continue; // already there -- ignore
  899             }
  900 
  901             bool found_matrix = false;
  902 
  903             ji = cJSON_GetObjectItem(js, "matrix");
  904 
  905             if (ji) {
  906                 if (ji->type != cJSON_Array) {
  907                     goto parse_error;
  908                 }
  909 
  910                 ji = ji->child;
  911 
  912                 for (int i = 0; i < 3; ++i) {
  913                     for (int j = 0; j < 3; ++j, ji = ji->next) {
  914                         if (!ji || ji->type != cJSON_Number) {
  915                             goto parse_error;
  916                         }
  917 
  918                         m->matrix[i][j] = ji->valuedouble;
  919                     }
  920                 }
  921 
  922                 if (ji) {
  923                     goto parse_error;
  924                 }
  925 
  926                 found_matrix = true;
  927             } else {
  928                 ji = cJSON_GetObjectItem(js, "file");
  929 
  930                 if (!ji || ji->type != cJSON_String) {
  931                     goto parse_error;
  932                 }
  933 
  934                 found_matrix = computeWorkingSpaceMatrix(path, ji->valuestring, *m);
  935             }
  936 
  937             if (!found_matrix) {
  938                 if (settings->verbose) {
  939                     std::cout << "Could not find suitable matrix for working space: " << name << std::endl;
  940                 }
  941 
  942                 continue;
  943             }
  944 
  945             pMatrices.emplace_back(std::move(m));
  946             TMatrix w = pMatrices.back()->matrix;
  947 
  948             CMatrix b = {};
  949 
  950             if (!rtengine::invertMatrix(pMatrices.back()->toMatrix(), b)) {
  951                 if (settings->verbose) {
  952                     std::cout << "Matrix for working space: " << name << " is not invertible, skipping" << std::endl;
  953                 }
  954 
  955                 pMatrices.pop_back();
  956             } else {
  957                 wMatrices[name] = w;
  958                 pMatrices.emplace_back(new PMatrix(b));
  959                 TMatrix iw = pMatrices.back()->matrix;
  960                 iwMatrices[name] = iw;
  961                 wProfiles[name] = createFromMatrix(w);
  962 
  963                 if (settings->verbose) {
  964                     std::cout << "Added working space: " << name << std::endl;
  965                     std::cout << "  matrix: [";
  966 
  967                     for (int i = 0; i < 3; ++i) {
  968                         std::cout << " [";
  969 
  970                         for (int j = 0; j < 3; ++j) {
  971                             std::cout << " " << w[i][j];
  972                         }
  973 
  974                         std::cout << "]";
  975                     }
  976 
  977                     std::cout << " ]" << std::endl;
  978                 }
  979             }
  980         }
  981 
  982         cJSON_Delete(root);
  983 
  984         if (settings->verbose) {
  985             std::cout << " OK" << std::endl;
  986         }
  987 
  988         return true;
  989 
  990 parse_error:
  991 
  992         if (settings->verbose) {
  993             std::cout << " ERROR in parsing " << fileName << std::endl;
  994         }
  995 
  996         cJSON_Delete(root);
  997         return false;
  998     }
  999 
 1000     using ProfileMap = std::map<Glib::ustring, cmsHPROFILE>;
 1001     using MatrixMap = std::map<Glib::ustring, TMatrix>;
 1002     using ContentMap = std::map<Glib::ustring, ProfileContent>;
 1003     using NameMap = std::map<Glib::ustring, Glib::ustring>;
 1004 
 1005     ProfileMap wProfiles;
 1006     // ProfileMap wProfilesGamma;
 1007     MatrixMap wMatrices;
 1008     MatrixMap iwMatrices;
 1009 
 1010     std::vector<std::unique_ptr<PMatrix>> pMatrices;
 1011 
 1012     // These contain profiles from user/system directory(supplied on init)
 1013     Glib::ustring profilesDir;
 1014     Glib::ustring userICCDir;
 1015     ProfileMap fileProfiles;
 1016     ContentMap fileProfileContents;
 1017 
 1018     //These contain standard profiles from RT. Keys are all in uppercase.
 1019     Glib::ustring stdProfilesDir;
 1020     NameMap fileStdProfilesFileNames;
 1021     ProfileMap fileStdProfiles;
 1022 
 1023     Glib::ustring defaultMonitorProfile;
 1024 
 1025     bool loadAll;
 1026 
 1027     const cmsHPROFILE xyz;
 1028     const cmsHPROFILE srgb;
 1029 
 1030     mutable MyMutex mutex;
 1031 };
 1032 
 1033 rtengine::ICCStore* rtengine::ICCStore::getInstance()
 1034 {
 1035     static rtengine::ICCStore instance;
 1036     return &instance;
 1037 }
 1038 
 1039 void rtengine::ICCStore::init(const Glib::ustring& usrICCDir, const Glib::ustring& stdICCDir, bool loadAll)
 1040 {
 1041     implementation->init(usrICCDir, stdICCDir, loadAll);
 1042 }
 1043 
 1044 cmsHPROFILE rtengine::ICCStore::workingSpace(const Glib::ustring& name) const
 1045 {
 1046     return implementation->workingSpace(name);
 1047 }
 1048 
 1049 // cmsHPROFILE rtengine::ICCStore::workingSpaceGamma(const Glib::ustring& name) const
 1050 // {
 1051 //     return implementation->workingSpaceGamma(name);
 1052 // }
 1053 
 1054 rtengine::TMatrix rtengine::ICCStore::workingSpaceMatrix(const Glib::ustring& name) const
 1055 {
 1056     return implementation->workingSpaceMatrix(name);
 1057 }
 1058 
 1059 rtengine::TMatrix rtengine::ICCStore::workingSpaceInverseMatrix(const Glib::ustring& name) const
 1060 {
 1061     return implementation->workingSpaceInverseMatrix(name);
 1062 }
 1063 
 1064 bool rtengine::ICCStore::outputProfileExist(const Glib::ustring& name) const
 1065 {
 1066     return implementation->outputProfileExist(name);
 1067 }
 1068 
 1069 cmsHPROFILE rtengine::ICCStore::getProfile(const Glib::ustring& name) const
 1070 {
 1071     return implementation->getProfile(name);
 1072 }
 1073 
 1074 cmsHPROFILE rtengine::ICCStore::getStdProfile(const Glib::ustring& name) const
 1075 {
 1076     return implementation->getStdProfile(name);
 1077 }
 1078 
 1079 rtengine::ProfileContent rtengine::ICCStore::getContent(const Glib::ustring& name) const
 1080 {
 1081     return implementation->getContent(name);
 1082 }
 1083 
 1084 
 1085 Glib::ustring rtengine::ICCStore::getDefaultMonitorProfileName() const
 1086 {
 1087     return implementation->getDefaultMonitorProfileName();
 1088 }
 1089 
 1090 
 1091 void rtengine::ICCStore::setDefaultMonitorProfileName(const Glib::ustring &name)
 1092 {
 1093     implementation->setDefaultMonitorProfileName(name);
 1094 }
 1095 
 1096 cmsHPROFILE rtengine::ICCStore::getXYZProfile() const
 1097 {
 1098     return implementation->getXYZProfile();
 1099 }
 1100 
 1101 cmsHPROFILE rtengine::ICCStore::getsRGBProfile() const
 1102 {
 1103     return implementation->getsRGBProfile();
 1104 }
 1105 
 1106 std::vector<Glib::ustring> rtengine::ICCStore::getProfiles(ProfileType type) const
 1107 {
 1108     return implementation->getProfiles(type);
 1109 }
 1110 
 1111 std::vector<Glib::ustring> rtengine::ICCStore::getProfilesFromDir(const Glib::ustring& dirName) const
 1112 {
 1113     return implementation->getProfilesFromDir(dirName);
 1114 }
 1115 
 1116 std::uint8_t rtengine::ICCStore::getInputIntents(cmsHPROFILE profile) const
 1117 {
 1118     return implementation->getInputIntents(profile);
 1119 }
 1120 
 1121 std::uint8_t rtengine::ICCStore::getOutputIntents(cmsHPROFILE profile) const
 1122 {
 1123     return implementation->getOutputIntents(profile);
 1124 }
 1125 
 1126 std::uint8_t rtengine::ICCStore::getProofIntents(cmsHPROFILE profile) const
 1127 {
 1128     return implementation->getProofIntents(profile);
 1129 }
 1130 
 1131 std::uint8_t rtengine::ICCStore::getInputIntents(const Glib::ustring& name) const
 1132 {
 1133     return implementation->getInputIntents(name);
 1134 }
 1135 
 1136 std::uint8_t rtengine::ICCStore::getOutputIntents(const Glib::ustring& name) const
 1137 {
 1138     return implementation->getOutputIntents(name);
 1139 }
 1140 
 1141 std::uint8_t rtengine::ICCStore::getProofIntents(const Glib::ustring& name) const
 1142 {
 1143     return implementation->getProofIntents(name);
 1144 }
 1145 
 1146 rtengine::ICCStore::ICCStore() :
 1147     implementation(new Implementation)
 1148 {
 1149 }
 1150 
 1151 rtengine::ICCStore::~ICCStore() = default;
 1152 
 1153 std::vector<Glib::ustring> rtengine::ICCStore::getWorkingProfiles()
 1154 {
 1155     return implementation->getWorkingProfiles();
 1156 }
 1157 
 1158 // WARNING: the caller must lock lcmsMutex
 1159 cmsHPROFILE rtengine::ICCStore::makeStdGammaProfile(cmsHPROFILE iprof)
 1160 {
 1161     // forgive me for the messy code, quick hack to change gamma of an ICC profile to the RT standard gamma
 1162     if (!iprof) {
 1163         return nullptr;
 1164     }
 1165 
 1166     cmsUInt32Number bytesNeeded = 0;
 1167     cmsSaveProfileToMem(iprof, nullptr, &bytesNeeded);
 1168 
 1169     if (bytesNeeded == 0) {
 1170         return nullptr;
 1171     }
 1172 
 1173     uint8_t *data = new uint8_t[bytesNeeded + 1];
 1174     cmsSaveProfileToMem(iprof, data, &bytesNeeded);
 1175     const uint8_t *p = &data[128]; // skip 128 byte header
 1176     uint32_t tag_count;
 1177     memcpy(&tag_count, p, 4);
 1178     p += 4;
 1179     tag_count = ntohl(tag_count);
 1180 
 1181     struct icctag {
 1182         uint32_t sig;
 1183         uint32_t offset;
 1184         uint32_t size;
 1185     } tags[tag_count];
 1186 
 1187     constexpr uint32_t gamma = 0x239;
 1188     constexpr int gamma_size = 14;
 1189     int data_size = (gamma_size + 3) & ~3;
 1190 
 1191     for (uint32_t i = 0; i < tag_count; i++) {
 1192         memcpy(&tags[i], p, 12);
 1193         tags[i].sig = ntohl(tags[i].sig);
 1194         tags[i].offset = ntohl(tags[i].offset);
 1195         tags[i].size = ntohl(tags[i].size);
 1196         p += 12;
 1197 
 1198         if (tags[i].sig != 0x62545243 && // bTRC
 1199                 tags[i].sig != 0x67545243 && // gTRC
 1200                 tags[i].sig != 0x72545243 && // rTRC
 1201                 tags[i].sig != 0x6B545243) { // kTRC
 1202             data_size += (tags[i].size + 3) & ~3;
 1203         }
 1204     }
 1205 
 1206     uint32_t sz = 128 + 4 + tag_count * 12 + data_size;
 1207     uint8_t *nd = new uint8_t[sz];
 1208     memset(nd, 0, sz);
 1209     memcpy(nd, data, 128 + 4);
 1210     sz = htonl(sz);
 1211     memcpy(nd, &sz, 4);
 1212     uint32_t offset = 128 + 4 + tag_count * 12;
 1213     uint32_t gamma_offset = 0;
 1214 
 1215     for (uint32_t i = 0; i < tag_count; i++) {
 1216         struct icctag tag;
 1217         tag.sig = htonl(tags[i].sig);
 1218 
 1219         if (tags[i].sig == 0x62545243 || // bTRC
 1220                 tags[i].sig == 0x67545243 || // gTRC
 1221                 tags[i].sig == 0x72545243 || // rTRC
 1222                 tags[i].sig == 0x6B545243) { // kTRC
 1223             if (gamma_offset == 0) {
 1224                 gamma_offset = offset;
 1225                 uint32_t pcurve[] = { htonl(0x63757276), htonl(0), htonl(/*gamma_size == 12 ? 0U : */1U) };
 1226                 memcpy(&nd[offset], pcurve, 12);
 1227 
 1228                 //if (gamma_size == 14) {
 1229                 uint16_t gm = htons(gamma);
 1230                 memcpy(&nd[offset + 12], &gm, 2);
 1231                 //}
 1232 
 1233                 offset += (gamma_size + 3) & ~3;
 1234             }
 1235 
 1236             tag.offset = htonl(gamma_offset);
 1237             tag.size = htonl(gamma_size);
 1238         } else {
 1239             tag.offset = htonl(offset);
 1240             tag.size = htonl(tags[i].size);
 1241             memcpy(&nd[offset], &data[tags[i].offset], tags[i].size);
 1242             offset += (tags[i].size + 3) & ~3;
 1243         }
 1244 
 1245         memcpy(&nd[128 + 4 + i * 12], &tag, 12);
 1246     }
 1247 
 1248     cmsHPROFILE oprof = cmsOpenProfileFromMem(nd, ntohl(sz));
 1249     delete [] nd;
 1250     delete [] data;
 1251     return oprof;
 1252 }
 1253 
 1254 cmsHPROFILE rtengine::ICCStore::createFromMatrix(const double matrix[3][3], bool gamma, const Glib::ustring& name)
 1255 {
 1256 
 1257     static const unsigned phead[] = {
 1258         1024, 0, 0x2100000, 0x6d6e7472, 0x52474220, 0x58595a20, 0, 0, 0,
 1259         0x61637370, 0, 0, 0, 0, 0, 0, 0, 0xf6d6, 0x10000, 0xd32d
 1260     };
 1261     unsigned pbody[] = {
 1262         10, 0x63707274, 0, 36,  /* cprt */
 1263         0x64657363, 0, 40,  /* desc */
 1264         0x77747074, 0, 20,  /* wtpt */
 1265         0x626b7074, 0, 20,  /* bkpt */
 1266         0x72545243, 0, 14,  /* rTRC */
 1267         0x67545243, 0, 14,  /* gTRC */
 1268         0x62545243, 0, 14,  /* bTRC */
 1269         0x7258595a, 0, 20,  /* rXYZ */
 1270         0x6758595a, 0, 20,  /* gXYZ */
 1271         0x6258595a, 0, 20
 1272     };    /* bXYZ */
 1273     static const unsigned pwhite[] = { 0xf351, 0x10000, 0x116cc };//D65
 1274     //static const unsigned pwhite[] = { 0xf6d6, 0x10000, 0xd340 };//D50
 1275 
 1276     // 0x63757276 : curveType, 0 : reserved, 1 : entries(1=gamma, 0=identity), 0x1000000=1.0
 1277     unsigned pcurve[] = { 0x63757276, 0, 0, 0x1000000 };
 1278 //    unsigned pcurve[] = { 0x63757276, 0, 1, 0x1000000 };
 1279 
 1280     if (gamma) {
 1281         pcurve[2] = 1;
 1282         // pcurve[3] = 0x1f00000;// pcurve for gamma BT709 : g=2.22 s=4.5
 1283         // normalize gamma in RT, default(Emil's choice = sRGB)
 1284         pcurve[3] = 0x2390000;//pcurve for gamma sRGB : g:2.4 s=12.92310
 1285 
 1286     } else {
 1287         // lcms2 up to 2.4 has a bug with linear gamma causing precision loss(banding)
 1288         // of floating point data when a normal icc encoding of linear gamma is used
 1289         //(i e 0 table entries), but by encoding a gamma curve which is 1.0 the
 1290         // floating point path is taken within lcms2 so no precision loss occurs and
 1291         // gamma is still 1.0.
 1292         pcurve[2] = 1;
 1293         pcurve[3] = 0x1000000; //pcurve for gamma 1
 1294     }
 1295 
 1296     // constructing profile header
 1297     unsigned* oprof = new unsigned [phead[0] / sizeof(unsigned)];
 1298     memset(oprof, 0, phead[0]);
 1299     memcpy(oprof, phead, sizeof(phead));
 1300 
 1301     oprof[0] = 132 + 12 * pbody[0];
 1302 
 1303     // constructing tag directory(pointers inside the file), and types
 1304     // 0x74657874 : text
 1305     // 0x64657363 : description tag
 1306     for (unsigned int i = 0; i < pbody[0]; i++) {
 1307         oprof[oprof[0] / 4] = i ? (i > 1 ? 0x58595a20 : 0x64657363) : 0x74657874;
 1308         pbody[i * 3 + 2] = oprof[0];
 1309         oprof[0] += (pbody[i * 3 + 3] + 3) & -4;
 1310     }
 1311 
 1312     memcpy(oprof + 32, pbody, sizeof(pbody));
 1313 
 1314     // wtpt
 1315     memcpy((char *)oprof + pbody[8] + 8, pwhite, sizeof(pwhite));
 1316 
 1317     // r/g/b TRC
 1318     for (int i = 4; i < 7; i++) {
 1319         memcpy((char *)oprof + pbody[i * 3 + 2], pcurve, sizeof(pcurve));
 1320     }
 1321 
 1322     // r/g/b XYZ
 1323 //    pseudoinverse((double(*)[3]) out_rgb[output_color-1], inverse, 3);
 1324     for (int i = 0; i < 3; i++)
 1325         for (int j = 0; j < 3; j++) {
 1326             oprof[pbody[j * 3 + 23] / 4 + i + 2] = matrix[i][j] * 0x10000 + 0.5;
 1327 //      for (num = k=0; k < 3; k++)
 1328 //        num += xyzd50_srgb[i][k] * inverse[j][k];
 1329         }
 1330 
 1331     // convert to network byte order
 1332     for (unsigned int i = 0; i < phead[0] / 4; i++) {
 1333         oprof[i] = htonl(oprof[i]);
 1334     }
 1335 
 1336     // cprt
 1337     strcpy((char *)oprof + pbody[2] + 8, "--rawtherapee profile--");
 1338 
 1339     // desc
 1340     oprof[pbody[5] / 4 + 2] = name.size() + 1;
 1341     strcpy((char *)oprof + pbody[5] + 12, name.c_str());
 1342 
 1343 
 1344     cmsHPROFILE p = cmsOpenProfileFromMem(oprof, ntohl(oprof[0]));
 1345     delete [] oprof;
 1346     return p;
 1347 }