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