1 /*
2   File:       create_CLUT_profile_from_probe.cpp
3 
4   Contains:   Command-line app that takes the pathname of a screen grab
5   of a probe image, and the pixels coordinates within that image
6   of the white border of the content area, and extracts from the
7   pixels within the border area the non-ICC-color-managed values
8   of the (known) probe pixel values, establishing a relationship
9   between that non-ICC system's un-color-managed and color-managed
10   pixel values.
11 
12   Version:    V1
13 
14   Copyright:  � see below
15 */
16 
17 /*
18  * The ICC Software License, Version 0.2
19  *
20  *
21  * Copyright (c) 2003-2010 The International Color Consortium. All rights
22  * reserved.
23  *
24  * Redistribution and use in source and binary forms, with or without
25  * modification, are permitted provided that the following conditions
26  * are met:
27  *
28  * 1. Redistributions of source code must retain the above copyright
29  *    notice, this list of conditions and the following disclaimer.
30  *
31  * 2. Redistributions in binary form must reproduce the above copyright
32  *    notice, this list of conditions and the following disclaimer in
33  *    the documentation and/or other materials provided with the
34  *    distribution.
35  *
36  * 3. In the absence of prior written permission, the names "ICC" and "The
37  *    International Color Consortium" must not be used to imply that the
38  *    ICC organization endorses or promotes products derived from this
39  *    software.
40  *
41  *
42  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
43  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
44  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
45  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNATIONAL COLOR CONSORTIUM OR
46  * ITS CONTRIBUTING MEMBERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
48  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
49  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
50  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
51  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
52  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
53  * SUCH DAMAGE.
54  * ====================================================================
55  *
56  * This software consists of voluntary contributions made by many
57  * individuals on behalf of the The International Color Consortium.
58  *
59  *
60  * Membership in the ICC is encouraged when this software is used for
61  * commercial purposes.
62  *
63  *
64  * For more information on The International Color Consortium, please
65  * see <http://www.color.org/>.
66  *
67  *
68  */
69 
70 //////////////////////////////////////////////////////////////////////
71 // HISTORY:
72 //
73 // -Initial implementation by Joseph Goldstone spring 2007
74 //
75 //////////////////////////////////////////////////////////////////////
76 
77 #include <time.h>
78 
79 #include <iostream>
80 #include <fstream>
81 #include <vector>
82 #include <sstream>
83 #include <exception>
84 #include <stdexcept>
85 #include <limits>
86 using namespace std;
87 
88 #include "ICC_tool_exception.h"
89 #include "Stubs.h"
90 #include "Vetters.h"
91 
92 #include "IccDefs.h"
93 #include "IccProfile.h"
94 #include "IccTagLut.h"
95 #include "IccUtil.h"
96 #include "IccCmm.h"
97 
98 vector<DeviceRGB>
RGBs_from_probe_pathname(const char * const grabbed_probe_RGB_values_path)99 RGBs_from_probe_pathname(const char* const grabbed_probe_RGB_values_path)
100 {
101   ifstream iS(grabbed_probe_RGB_values_path);
102   if (! iS)
103   {
104     ostringstream oSS;
105     oSS << "Couldn't open grabbed probe RGB values path `"
106         << grabbed_probe_RGB_values_path << "'";
107     throw runtime_error(oSS.str());
108   }
109   double uninitialized = numeric_limits<double>::max();
110   string line("");
111   vector<DeviceRGB> triplets;
112   while(getline(iS, line))
113   {
114     if (iS.eof()) break;
115     double r = uninitialized;
116     double g = uninitialized;
117     double b = uninitialized;
118     if (string("") == line)
119       continue;
120     istringstream iSS(line);
121     iSS >> r >> g >> b;
122     if (r == uninitialized || g == uninitialized || b == uninitialized)
123       throw runtime_error("malformed input line.");
124     triplets.push_back(DeviceRGB(r, g, b));
125   }
126   return triplets;
127 }
128 
129 void
loadInputShaperLUTs(CIccTagCurve ** inputShaperLUTs,const std::string & inputShaperFilename)130 loadInputShaperLUTs(CIccTagCurve** inputShaperLUTs,
131                     const std::string& inputShaperFilename)
132 {
133   ifstream s(inputShaperFilename.c_str());
134   if (! s)
135   {
136     ostringstream os;
137     os << "Could not load input shaper LUTs from `" << inputShaperFilename
138        << "'";
139     throw runtime_error(os.str());
140   }
141 
142   string maxChannelValueAsString;
143   s >> maxChannelValueAsString;
144   int maxChannelValue = atoi(maxChannelValueAsString.c_str());
145   vector<double> redVals;
146   vector<double> greenVals;
147   vector<double> blueVals;
148   string line("");
149   while (getline(s, line))
150   {
151     if (line == "")
152       continue;
153     double redVal;
154     double greenVal;
155     double blueVal;
156     istringstream is(line);
157     is >> redVal;
158     is >> greenVal;
159     is >> blueVal;
160     redVals.push_back(redVal);
161     greenVals.push_back(greenVal);
162     blueVals.push_back(blueVal);
163   }
164   unsigned int numEntries = (unsigned int)redVals.size();
165   // now make the LUT objects of the appropriate length and stuff them.
166   CIccTagCurve*   redCurve = inputShaperLUTs[0];
167   CIccTagCurve* greenCurve = inputShaperLUTs[1];
168   CIccTagCurve*  blueCurve = inputShaperLUTs[2];
169   redCurve->SetSize(numEntries, icInitIdentity);
170   greenCurve->SetSize(numEntries, icInitIdentity);
171   blueCurve->SetSize(numEntries, icInitIdentity);
172   for (unsigned int j = 0, N = numEntries; j < N; ++j)
173   {
174     (  *redCurve)[j] = static_cast<icFloatNumber>(  redVals[j] / maxChannelValue);
175     (*greenCurve)[j] = static_cast<icFloatNumber>(greenVals[j] / maxChannelValue);
176     ( *blueCurve)[j] = static_cast<icFloatNumber>( blueVals[j] / maxChannelValue);
177   }
178 }
179 
180 class Simple_CLUT_stuffer : public IIccCLUTExec
181 {
182 public:
Simple_CLUT_stuffer(int edge_N,const vector<CIEXYZ> & PCS_XYZs)183   Simple_CLUT_stuffer(int edge_N, const vector<CIEXYZ>& PCS_XYZs)
184     : edge_N_(edge_N), PCS_XYZs_(PCS_XYZs)
185   {
186   }
187 
188   void
PixelOp(icFloatNumber * pGridAdr,icFloatNumber * pData)189   PixelOp(icFloatNumber* pGridAdr, icFloatNumber* pData)
190   {
191     int r_idx = static_cast<int>(pGridAdr[0] * (edge_N_ - 1) + 0.5);
192     int g_idx = static_cast<int>(pGridAdr[1] * (edge_N_ - 1) + 0.5);
193     int b_idx = static_cast<int>(pGridAdr[2] * (edge_N_ - 1) + 0.5);
194     int flat_idx = r_idx * edge_N_ * edge_N_ + g_idx * edge_N_ + b_idx;
195     pData[0] = static_cast<icFloatNumber>(PCS_XYZs_[flat_idx].X());
196     pData[1] = static_cast<icFloatNumber>(PCS_XYZs_[flat_idx].Y());
197     pData[2] = static_cast<icFloatNumber>(PCS_XYZs_[flat_idx].Z());
198     //    cout << "r_idx " << r_idx << " g_idx " << g_idx << " b_idx " << b_idx
199     //      << " flat_idx " << flat_idx
200     //      << " CIEXYZ(" << pData[0] << ", " << pData[1] << ", " << pData[2] << ")\n";
201     icXyzToPcs(pData);
202     //    icFloatNumber PCS_XYZ[3];
203     //    PCS_XYZ[0] = PCS_XYZs_[flat_idx].X();
204     //    PCS_XYZ[1] = PCS_XYZs_[flat_idx].Y();
205     //    PCS_XYZ[2] = PCS_XYZs_[flat_idx].Z();
206     //    icXYZtoLab(pData, PCS_XYZ, icD50XYZ);
207     //    icLabToPcs(pData);
208     //    CIccPCS::Lab4ToLab2(pData, pData);
209   }
210 
211 private:
212   const int edge_N_;
213   const vector<CIEXYZ> PCS_XYZs_;
214 };
215 
216 CIccTagLut16*
make_A2Bx_tag(int edge_N,const vector<CIEXYZ> PCS_XYZs,const char * const input_shaper_filename)217 make_A2Bx_tag(int edge_N, const vector<CIEXYZ> PCS_XYZs,
218               const char* const input_shaper_filename)
219 {
220   CIccTagLut16* lut16 = new CIccTagLut16();
221   lut16->Init(3, 3);
222   lut16->SetColorSpaces(icSigRgbData, icSigLabData);
223 
224   lut16->NewMatrix();
225 
226   LPIccCurve* iLUT = lut16->NewCurvesA();
227   for (int i = 0; i < 3; ++i)
228   {
229     CIccTagCurve* pCurve = new CIccTagCurve(0);
230     pCurve->SetSize(2, icInitIdentity);
231     iLUT[i] = pCurve;
232   }
233 
234   CIccCLUT* CLUT = lut16->NewCLUT(edge_N);
235   Simple_CLUT_stuffer* stuffer = new Simple_CLUT_stuffer(edge_N, PCS_XYZs);
236   CLUT->Iterate(stuffer);
237 
238   LPIccCurve* oLUT = lut16->NewCurvesB();
239   if (strcmp("", input_shaper_filename) == 0)
240     for (int i = 0; i < 3; ++i)
241     {
242       CIccTagCurve* pCurve = new CIccTagCurve(0);
243       pCurve->SetSize(2, icInitIdentity);
244       oLUT[i] = pCurve;
245     }
246   else
247   {
248     CIccTagCurve* inputShaperLUTs[3];
249     for (int i = 0; i < 3; ++i)
250     {
251       inputShaperLUTs[i] = new CIccTagCurve(0);
252       inputShaperLUTs[i]->SetSize(2, icInitIdentity);
253     }
254     loadInputShaperLUTs(inputShaperLUTs, input_shaper_filename);
255     for (int i = 0; i < 3; ++i)
256       oLUT[i] = inputShaperLUTs[i];
257   }
258   return lut16;
259 }
260 
261 vector<CIEXYZ>
XYZs_from_monitor_RGBs(const vector<DeviceRGB> & RGBs,CIccCmm * cmm)262 XYZs_from_monitor_RGBs(const vector<DeviceRGB>& RGBs, CIccCmm* cmm)
263 {
264   vector<CIEXYZ> XYZs;
265   for (vector<DeviceRGB>::const_iterator
266          iter = RGBs.begin(), end_iter = RGBs.end(); iter != end_iter; ++iter)
267   {
268     icFloatNumber RGB[3];
269     RGB[0] = static_cast<icFloatNumber>(iter->R());
270     RGB[1] = static_cast<icFloatNumber>(iter->G());
271     RGB[2] = static_cast<icFloatNumber>(iter->B());
272     icFloatNumber XYZ[3];
273     cmm->Apply(XYZ, RGB);
274     icXyzFromPcs(XYZ);
275     //    cout << "RGB(" << RGB[0] << ", " << RGB[1] << ", " << RGB[2] << ") -> XYZ("
276     //      << XYZ[0] << ", " << XYZ[1] << ", " << XYZ[2] << ")\n";
277     XYZs.push_back(CIEXYZ(XYZ[0], XYZ[1], XYZ[2]));
278   }
279   return XYZs;
280 }
281 
282 void
usage(ostream & s,const char * const myName)283 usage(ostream& s, const char* const myName)
284 {
285   s << myName << ": usage is " << myName
286   << " N probe monitor description path copyright [pretransform]\n"
287   << "where\n"
288   << " N is the length of the sides of the flattened probe cube\n"
289   << " probe is the pathname of the readable, "
290   << " nonzero-length file containing the screen grab of the flattened"
291   << " probe cube\n"
292   << " monitor is the pathname of the ICC profile which was"
293   << " active for the screen on which the flattened probe cube screen grab"
294   << " was made\n"
295   << " description is a text description of the profile that"
296   << " will be used to populate profile menus in applications, e.g. "
297   << " in Photoshop - remember, if there are spaces embedded in your"
298   << " description, then your description should be in quotes\n"
299   << " path is the pathname to which the created CLUT input"
300   << " profile will be written\n"
301   << " copyright is a string to be embedded in the profile indicating"
302   << " ownership - remember, if it contains spaces, the string must be in"
303   << " quotes\n"
304   << " pretransform, an optional argument, is a pathname containing"
305   << " pretransform curve expressed as lines of triplets of shaper LUT"
306   << " contents in the [0, 1024) range\n";
307 }
308 
309 int
main(int argc,char * argv[])310 main(int argc, char* argv[])
311 {
312   const char* const my_name = path_tail(argv[0]);
313   if (argc < 7 || argc > 8)
314   {
315     usage(cout, my_name);
316     return EXIT_FAILURE;
317   }
318   try
319   {
320     const char* const edge_size_string = argv[1];
321     vet_as_int(edge_size_string, "N", "the length of the sides of the"
322       " flattened probe cube");
323     size_t N = atoi(edge_size_string);
324     if (N < 1)
325       throw ICC_tool_exception("length of the sides of the flattened probe must"
326                                " be positive (and really should be at least 11,"
327                                " preferably considerably more than that.)");
328 
329     const char * const grabbed_probe_values_pathname = argv[2];
330     vet_input_file_pathname(grabbed_probe_values_pathname,
331       "probe", "the pathname of the readable,"
332       " nonzero-length text file containing the values extracted from the"
333       " screen grab of the flattened probe cube");
334     vector<DeviceRGB> device_RGBs
335       (RGBs_from_probe_pathname(grabbed_probe_values_pathname));
336 
337     const char* const monitor_profile_pathname = argv[3];
338     vet_input_file_pathname(monitor_profile_pathname,
339       "monitor", "the pathname of the ICC profile which was active"
340       " for the screen on which the flattened probe cube screen grab"
341       " was made");
342 
343     const char* const input_profile_description = argv[4];
344 
345     const char* const input_profile_pathname = argv[5];
346     vet_output_file_pathname(input_profile_pathname,
347       "path", "the pathname of the file to which the created CLUT input"
348       " profile will be written, with the directory component of that"
349       " pathname being writable by the current user");
350 
351     const char* const copyright_holder = argv[6];
352     char year[5];
353     const time_t now = time(NULL);
354     strftime(year, 5, "%G", localtime(&now));
355     const char* const copyright_pattern("copyright (c) %s %s");
356     char copyright[256];
357     sprintf(copyright, copyright_pattern, copyright_holder, year);
358 
359     bool pretransform_pathname_specified = argc == 8;
360     const char* pretransform_pathname = "";
361     if (pretransform_pathname_specified)
362     {
363       pretransform_pathname = argv[7];
364       vet_input_file_pathname(pretransform_pathname,
365         "pretransform",
366         "pathname containing pretransform curve expressed as lines of triplets"
367         " of shaper LUT contents in [0, 1024)");
368     }
369 
370     CIccProfile* monitor_profile = ReadIccProfile(monitor_profile_pathname);
371     // +++ check result here
372 
373     CIccCmm* cmm = new CIccCmm();
374     icRenderingIntent monitor_rendering_intent
375       = static_cast<icRenderingIntent>(monitor_profile->m_Header.renderingIntent);
376     if (cmm->AddXform(monitor_profile_pathname,
377                       monitor_rendering_intent) != icCmmStatOk)
378     {
379       ostringstream s;
380       s << "Can't set profile `" << monitor_profile_pathname
381         << "' as initial CMM profile";
382       throw runtime_error(s.str());
383     }
384     if (cmm->Begin() != icCmmStatOk)
385     {
386       throw runtime_error("Can't start CMM");
387     }
388 
389     vector<CIEXYZ> PCS_XYZs_from_probe(XYZs_from_monitor_RGBs(device_RGBs, cmm));
390 
391     CIccProfile input_profile;
392     input_profile.InitHeader();
393     input_profile.m_Header.deviceClass     = icSigInputClass;
394     input_profile.m_Header.platform        = icSigMacintosh;
395     input_profile.m_Header.colorSpace      = icSigRgbData;
396     input_profile.m_Header.pcs             = icSigXYZData;
397     input_profile.m_Header.attributes      = static_cast<icUInt64Number>(icTransparency);
398     input_profile.m_Header.renderingIntent = monitor_profile->m_Header.renderingIntent;
399 
400     // Required tags for an N-component LUT-based input profile, as layed out in
401     // the ICC spec [sections 8.2 and 8.3.2] are:
402     //   profileDescriptionTag
403     //   copyrightTag
404     //   mediaWhitePointTag
405     //   chromaticAdaptationTag
406     //   A2B0 tag
407     // As it happens, not only are those ordered by their appearance in section
408     // 8.2 and 8.3.2, they are pretty much also ordered in increasing complexity.
409 
410     // profileDescriptionTag
411     CIccLocalizedUnicode USAEnglishDesc;
412     USAEnglishDesc.SetText((string("(grabbed) ") + input_profile_description).c_str());
413     CIccTagMultiLocalizedUnicode* descriptionTag = new CIccTagMultiLocalizedUnicode;
414     descriptionTag->m_Strings = new CIccMultiLocalizedUnicode; // dtor does deletion
415     descriptionTag->m_Strings->push_back(USAEnglishDesc);
416     input_profile.AttachTag(icSigProfileDescriptionTag, descriptionTag);
417 
418     // copyrightTag
419     CIccLocalizedUnicode USAEnglishCopyright;
420     USAEnglishCopyright.SetText(copyright);
421     CIccTagMultiLocalizedUnicode* copyrightTag = new CIccTagMultiLocalizedUnicode;
422     copyrightTag->m_Strings = new CIccMultiLocalizedUnicode; // dtor does deletion
423     copyrightTag->m_Strings->push_back(USAEnglishCopyright);
424     input_profile.AttachTag(icSigCopyrightTag, copyrightTag);
425 
426     CIccTagXYZ* white_point_tag
427       = static_cast<CIccTagXYZ*>(monitor_profile->FindTag(icSigMediaWhitePointTag));
428     input_profile.AttachTag(icSigMediaWhitePointTag, white_point_tag);
429 
430     CIccTagXYZ* black_point_tag
431       = static_cast<CIccTagXYZ*>(monitor_profile->FindTag(icSigMediaBlackPointTag));
432     // This is not a required tag, but if it's there, carry it over.
433     if (black_point_tag != NULL)
434       input_profile.AttachTag(icSigMediaBlackPointTag, black_point_tag);
435 
436     CIccTagXYZ* luminance_tag
437       = static_cast<CIccTagXYZ*>(monitor_profile->FindTag(icSigLuminanceTag));
438     if (luminance_tag != NULL)
439       input_profile.AttachTag(icSigLuminanceTag, luminance_tag);
440 
441 
442     CIccTagS15Fixed16* monitor_chromatic_adaptation_tag
443       = static_cast<CIccTagS15Fixed16*>(monitor_profile->FindTag(icSigChromaticAdaptationTag));
444     input_profile.AttachTag(icSigChromaticAdaptationTag,
445                             monitor_chromatic_adaptation_tag);
446 
447     if (PCS_XYZs_from_probe.size() != N * N * N)
448     {
449       ostringstream s;
450       s << "Edge size " << N << " implies a probe file with " << N *N * N
451         << " entries, but the file provided contains "
452         << PCS_XYZs_from_probe.size();
453       throw runtime_error(s.str());
454     }
455     // +++ input shaper LUT someday
456     CIccTagLut16* A2B0_tag = make_A2Bx_tag(N, PCS_XYZs_from_probe,
457                                            pretransform_pathname);
458     input_profile.AttachTag(icSigAToB0Tag, A2B0_tag);
459 
460 
461     //  CLUT* AToB0CLUT = new CLUT();
462     //  CIccTagLut16* AToB0Tag
463     //    = AToB0CLUT->makeAToBxTag(size, measuredXYZ, flare, illuminant, CATToD50,
464     //                              inputShaperGamma, inputShaperFilename,
465     //                              adaptedMediaWhite, LABPCS);
466     //  BlackScaler blackScaler(size, measuredXYZ, adaptedMediaBlack, adaptedMediaWhite);
467     //  AToB0CLUT->Iterate(&blackScaler);
468     //  profile.AttachTag(icSigAToB0Tag, AToB0Tag); // the A2B0 tag
469 
470     //Verify things
471     string validationReport;
472     icValidateStatus validationStatus = input_profile.Validate(validationReport);
473 
474     switch (validationStatus)
475     {
476       case icValidateOK:
477         break;
478 
479       case icValidateWarning:
480         clog << "Profile validation warning" << endl
481              << validationReport;
482         break;
483 
484       case icValidateNonCompliant:
485         clog << "Profile non compliancy" << endl
486              << validationReport;
487         break;
488 
489       case icValidateCriticalError:
490       default:
491         clog << "Profile Error" << endl
492              << validationReport;
493     }
494 
495     // Out it goes
496     CIccFileIO out;
497     out.Open(input_profile_pathname, "w+");
498     input_profile.Write(&out);
499     out.Close();
500     return EXIT_SUCCESS;
501   }
502   catch (const exception& e)
503   {
504     cout << "error: " << e.what() << endl;
505     return EXIT_FAILURE;
506   }
507 }
508