1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2010 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 //---------------------------------------------------------------------------------
25 //
26
27 #include "lcms2_internal.h"
28
29 // Virtual (built-in) profiles
30 // -----------------------------------------------------------------------------------
31
32 static
SetTextTags(cmsHPROFILE hProfile,const wchar_t * Description)33 cmsBool SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description)
34 {
35 cmsMLU *DescriptionMLU, *CopyrightMLU;
36 cmsBool rc = FALSE;
37 cmsContext ContextID = cmsGetProfileContextID(hProfile);
38
39 DescriptionMLU = cmsMLUalloc(ContextID, 1);
40 CopyrightMLU = cmsMLUalloc(ContextID, 1);
41
42 if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error;
43
44 if (!cmsMLUsetWide(DescriptionMLU, "en", "US", Description)) goto Error;
45 if (!cmsMLUsetWide(CopyrightMLU, "en", "US", L"No copyright, use freely")) goto Error;
46
47 if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Error;
48 if (!cmsWriteTag(hProfile, cmsSigCopyrightTag, CopyrightMLU)) goto Error;
49
50 rc = TRUE;
51
52 Error:
53
54 if (DescriptionMLU)
55 cmsMLUfree(DescriptionMLU);
56 if (CopyrightMLU)
57 cmsMLUfree(CopyrightMLU);
58 return rc;
59 }
60
61
62 static
SetSeqDescTag(cmsHPROFILE hProfile,const char * Model)63 cmsBool SetSeqDescTag(cmsHPROFILE hProfile, const char* Model)
64 {
65 cmsBool rc = FALSE;
66 cmsContext ContextID = cmsGetProfileContextID(hProfile);
67 cmsSEQ* Seq = cmsAllocProfileSequenceDescription(ContextID, 1);
68
69 if (Seq == NULL) return FALSE;
70
71 Seq->seq[0].deviceMfg = (cmsSignature) 0;
72 Seq->seq[0].deviceModel = (cmsSignature) 0;
73
74 #ifdef CMS_DONT_USE_INT64
75 Seq->seq[0].attributes[0] = 0;
76 Seq->seq[0].attributes[1] = 0;
77 #else
78 Seq->seq[0].attributes = 0;
79 #endif
80
81 Seq->seq[0].technology = (cmsTechnologySignature) 0;
82
83 cmsMLUsetASCII( Seq->seq[0].Manufacturer, cmsNoLanguage, cmsNoCountry, "Little CMS");
84 cmsMLUsetASCII( Seq->seq[0].Model, cmsNoLanguage, cmsNoCountry, Model);
85
86 if (!_cmsWriteProfileSequence(hProfile, Seq)) goto Error;
87
88 rc = TRUE;
89
90 Error:
91 if (Seq)
92 cmsFreeProfileSequenceDescription(Seq);
93
94 return rc;
95 }
96
97
98
99 // This function creates a profile based on White point, primaries and
100 // transfer functions.
cmsCreateRGBProfileTHR(cmsContext ContextID,const cmsCIExyY * WhitePoint,const cmsCIExyYTRIPLE * Primaries,cmsToneCurve * const TransferFunction[3])101 cmsHPROFILE CMSEXPORT cmsCreateRGBProfileTHR(cmsContext ContextID,
102 const cmsCIExyY* WhitePoint,
103 const cmsCIExyYTRIPLE* Primaries,
104 cmsToneCurve* const TransferFunction[3])
105 {
106 cmsHPROFILE hICC;
107 cmsMAT3 MColorants;
108 cmsCIEXYZTRIPLE Colorants;
109 cmsCIExyY MaxWhite;
110 cmsMAT3 CHAD;
111 cmsCIEXYZ WhitePointXYZ;
112
113 hICC = cmsCreateProfilePlaceholder(ContextID);
114 if (!hICC) // can't allocate
115 return NULL;
116
117 cmsSetProfileVersion(hICC, 4.2);
118
119 cmsSetDeviceClass(hICC, cmsSigDisplayClass);
120 cmsSetColorSpace(hICC, cmsSigRgbData);
121 cmsSetPCS(hICC, cmsSigXYZData);
122
123 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL);
124
125
126 // Implement profile using following tags:
127 //
128 // 1 cmsSigProfileDescriptionTag
129 // 2 cmsSigMediaWhitePointTag
130 // 3 cmsSigRedColorantTag
131 // 4 cmsSigGreenColorantTag
132 // 5 cmsSigBlueColorantTag
133 // 6 cmsSigRedTRCTag
134 // 7 cmsSigGreenTRCTag
135 // 8 cmsSigBlueTRCTag
136 // 9 Chromatic adaptation Tag
137 // This conforms a standard RGB DisplayProfile as says ICC, and then I add (As per addendum II)
138 // 10 cmsSigChromaticityTag
139
140
141 if (!SetTextTags(hICC, L"RGB built-in")) goto Error;
142
143 if (WhitePoint) {
144
145 if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
146
147 cmsxyY2XYZ(&WhitePointXYZ, WhitePoint);
148 _cmsAdaptationMatrix(&CHAD, NULL, &WhitePointXYZ, cmsD50_XYZ());
149
150 // This is a V4 tag, but many CMM does read and understand it no matter which version
151 if (!cmsWriteTag(hICC, cmsSigChromaticAdaptationTag, (void*) &CHAD)) goto Error;
152 }
153
154 if (WhitePoint && Primaries) {
155
156 MaxWhite.x = WhitePoint -> x;
157 MaxWhite.y = WhitePoint -> y;
158 MaxWhite.Y = 1.0;
159
160 if (!_cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries)) goto Error;
161
162 Colorants.Red.X = MColorants.v[0].n[0];
163 Colorants.Red.Y = MColorants.v[1].n[0];
164 Colorants.Red.Z = MColorants.v[2].n[0];
165
166 Colorants.Green.X = MColorants.v[0].n[1];
167 Colorants.Green.Y = MColorants.v[1].n[1];
168 Colorants.Green.Z = MColorants.v[2].n[1];
169
170 Colorants.Blue.X = MColorants.v[0].n[2];
171 Colorants.Blue.Y = MColorants.v[1].n[2];
172 Colorants.Blue.Z = MColorants.v[2].n[2];
173
174 if (!cmsWriteTag(hICC, cmsSigRedColorantTag, (void*) &Colorants.Red)) goto Error;
175 if (!cmsWriteTag(hICC, cmsSigBlueColorantTag, (void*) &Colorants.Blue)) goto Error;
176 if (!cmsWriteTag(hICC, cmsSigGreenColorantTag, (void*) &Colorants.Green)) goto Error;
177 }
178
179
180 if (TransferFunction) {
181
182 if (!cmsWriteTag(hICC, cmsSigRedTRCTag, (void*) TransferFunction[0])) goto Error;
183 if (!cmsWriteTag(hICC, cmsSigGreenTRCTag, (void*) TransferFunction[1])) goto Error;
184 if (!cmsWriteTag(hICC, cmsSigBlueTRCTag, (void*) TransferFunction[2])) goto Error;
185 }
186
187 if (Primaries) {
188 if (!cmsWriteTag(hICC, cmsSigChromaticityTag, (void*) Primaries)) goto Error;
189 }
190
191
192 return hICC;
193
194 Error:
195 if (hICC)
196 cmsCloseProfile(hICC);
197 return NULL;
198 }
199
cmsCreateRGBProfile(const cmsCIExyY * WhitePoint,const cmsCIExyYTRIPLE * Primaries,cmsToneCurve * const TransferFunction[3])200 cmsHPROFILE CMSEXPORT cmsCreateRGBProfile(const cmsCIExyY* WhitePoint,
201 const cmsCIExyYTRIPLE* Primaries,
202 cmsToneCurve* const TransferFunction[3])
203 {
204 return cmsCreateRGBProfileTHR(NULL, WhitePoint, Primaries, TransferFunction);
205 }
206
207
208
209 // This function creates a profile based on White point and transfer function.
cmsCreateGrayProfileTHR(cmsContext ContextID,const cmsCIExyY * WhitePoint,const cmsToneCurve * TransferFunction)210 cmsHPROFILE CMSEXPORT cmsCreateGrayProfileTHR(cmsContext ContextID,
211 const cmsCIExyY* WhitePoint,
212 const cmsToneCurve* TransferFunction)
213 {
214 cmsHPROFILE hICC;
215 cmsCIEXYZ tmp;
216
217 hICC = cmsCreateProfilePlaceholder(ContextID);
218 if (!hICC) // can't allocate
219 return NULL;
220
221 cmsSetProfileVersion(hICC, 4.2);
222
223 cmsSetDeviceClass(hICC, cmsSigDisplayClass);
224 cmsSetColorSpace(hICC, cmsSigGrayData);
225 cmsSetPCS(hICC, cmsSigXYZData);
226 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL);
227
228
229 // Implement profile using following tags:
230 //
231 // 1 cmsSigProfileDescriptionTag
232 // 2 cmsSigMediaWhitePointTag
233 // 3 cmsSigGrayTRCTag
234
235 // This conforms a standard Gray DisplayProfile
236
237 // Fill-in the tags
238
239 if (!SetTextTags(hICC, L"gray built-in")) goto Error;
240
241
242 if (WhitePoint) {
243
244 cmsxyY2XYZ(&tmp, WhitePoint);
245 if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) &tmp)) goto Error;
246 }
247
248 if (TransferFunction) {
249
250 if (!cmsWriteTag(hICC, cmsSigGrayTRCTag, (void*) TransferFunction)) goto Error;
251 }
252
253 return hICC;
254
255 Error:
256 if (hICC)
257 cmsCloseProfile(hICC);
258 return NULL;
259 }
260
261
262
cmsCreateGrayProfile(const cmsCIExyY * WhitePoint,const cmsToneCurve * TransferFunction)263 cmsHPROFILE CMSEXPORT cmsCreateGrayProfile(const cmsCIExyY* WhitePoint,
264 const cmsToneCurve* TransferFunction)
265 {
266 return cmsCreateGrayProfileTHR(NULL, WhitePoint, TransferFunction);
267 }
268
269 // This is a devicelink operating in the target colorspace with as many transfer functions as components
270
cmsCreateLinearizationDeviceLinkTHR(cmsContext ContextID,cmsColorSpaceSignature ColorSpace,cmsToneCurve * const TransferFunctions[])271 cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLinkTHR(cmsContext ContextID,
272 cmsColorSpaceSignature ColorSpace,
273 cmsToneCurve* const TransferFunctions[])
274 {
275 cmsHPROFILE hICC;
276 cmsPipeline* Pipeline;
277 cmsStage* Lin;
278 int nChannels;
279
280 hICC = cmsCreateProfilePlaceholder(ContextID);
281 if (!hICC)
282 return NULL;
283
284 cmsSetProfileVersion(hICC, 4.2);
285
286 cmsSetDeviceClass(hICC, cmsSigLinkClass);
287 cmsSetColorSpace(hICC, ColorSpace);
288 cmsSetPCS(hICC, ColorSpace);
289
290 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL);
291
292 // Set up channels
293 nChannels = cmsChannelsOf(ColorSpace);
294
295 // Creates a Pipeline with prelinearization step only
296 Pipeline = cmsPipelineAlloc(ContextID, nChannels, nChannels);
297 if (Pipeline == NULL) goto Error;
298
299
300 // Copy tables to Pipeline
301 Lin = cmsStageAllocToneCurves(ContextID, nChannels, TransferFunctions);
302 if (Lin == NULL) goto Error;
303
304 cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, Lin);
305
306 // Create tags
307 if (!SetTextTags(hICC, L"Linearization built-in")) goto Error;
308 if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline)) goto Error;
309 if (!SetSeqDescTag(hICC, "Linearization built-in")) goto Error;
310
311 // Pipeline is already on virtual profile
312 cmsPipelineFree(Pipeline);
313
314 // Ok, done
315 return hICC;
316
317 Error:
318 if (hICC)
319 cmsCloseProfile(hICC);
320
321
322 return NULL;
323 }
324
cmsCreateLinearizationDeviceLink(cmsColorSpaceSignature ColorSpace,cmsToneCurve * const TransferFunctions[])325 cmsHPROFILE CMSEXPORT cmsCreateLinearizationDeviceLink(cmsColorSpaceSignature ColorSpace,
326 cmsToneCurve* const TransferFunctions[])
327 {
328 return cmsCreateLinearizationDeviceLinkTHR(NULL, ColorSpace, TransferFunctions);
329 }
330
331 // Ink-limiting algorithm
332 //
333 // Sum = C + M + Y + K
334 // If Sum > InkLimit
335 // Ratio= 1 - (Sum - InkLimit) / (C + M + Y)
336 // if Ratio <0
337 // Ratio=0
338 // endif
339 // Else
340 // Ratio=1
341 // endif
342 //
343 // C = Ratio * C
344 // M = Ratio * M
345 // Y = Ratio * Y
346 // K: Does not change
347
348 static
InkLimitingSampler(register const cmsUInt16Number In[],register cmsUInt16Number Out[],register void * Cargo)349 int InkLimitingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
350 {
351 cmsFloat64Number InkLimit = *(cmsFloat64Number *) Cargo;
352 cmsFloat64Number SumCMY, SumCMYK, Ratio;
353
354 InkLimit = (InkLimit * 655.35);
355
356 SumCMY = In[0] + In[1] + In[2];
357 SumCMYK = SumCMY + In[3];
358
359 if (SumCMYK > InkLimit) {
360
361 Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY);
362 if (Ratio < 0)
363 Ratio = 0;
364 }
365 else Ratio = 1;
366
367 Out[0] = _cmsQuickSaturateWord(In[0] * Ratio); // C
368 Out[1] = _cmsQuickSaturateWord(In[1] * Ratio); // M
369 Out[2] = _cmsQuickSaturateWord(In[2] * Ratio); // Y
370
371 Out[3] = In[3]; // K (untouched)
372
373 return TRUE;
374 }
375
376 // This is a devicelink operating in CMYK for ink-limiting
377
cmsCreateInkLimitingDeviceLinkTHR(cmsContext ContextID,cmsColorSpaceSignature ColorSpace,cmsFloat64Number Limit)378 cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLinkTHR(cmsContext ContextID,
379 cmsColorSpaceSignature ColorSpace,
380 cmsFloat64Number Limit)
381 {
382 cmsHPROFILE hICC;
383 cmsPipeline* LUT;
384 cmsStage* CLUT;
385 int nChannels;
386
387 if (ColorSpace != cmsSigCmykData) {
388 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "InkLimiting: Only CMYK currently supported");
389 return NULL;
390 }
391
392 if (Limit < 0.0 || Limit > 400) {
393
394 cmsSignalError(ContextID, cmsERROR_RANGE, "InkLimiting: Limit should be between 0..400");
395 if (Limit < 0) Limit = 0;
396 if (Limit > 400) Limit = 400;
397
398 }
399
400 hICC = cmsCreateProfilePlaceholder(ContextID);
401 if (!hICC) // can't allocate
402 return NULL;
403
404 cmsSetProfileVersion(hICC, 4.2);
405
406 cmsSetDeviceClass(hICC, cmsSigLinkClass);
407 cmsSetColorSpace(hICC, ColorSpace);
408 cmsSetPCS(hICC, ColorSpace);
409
410 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL);
411
412
413 // Creates a Pipeline with 3D grid only
414 LUT = cmsPipelineAlloc(ContextID, 4, 4);
415 if (LUT == NULL) goto Error;
416
417
418 nChannels = cmsChannelsOf(ColorSpace);
419
420 CLUT = cmsStageAllocCLut16bit(ContextID, 17, nChannels, nChannels, NULL);
421 if (CLUT == NULL) goto Error;
422
423 if (!cmsStageSampleCLut16bit(CLUT, InkLimitingSampler, (void*) &Limit, 0)) goto Error;
424
425 cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, nChannels));
426 cmsPipelineInsertStage(LUT, cmsAT_END, CLUT);
427 cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, nChannels));
428
429 // Create tags
430 if (!SetTextTags(hICC, L"ink-limiting built-in")) goto Error;
431
432 if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) LUT)) goto Error;
433 if (!SetSeqDescTag(hICC, "ink-limiting built-in")) goto Error;
434
435 // cmsPipeline is already on virtual profile
436 cmsPipelineFree(LUT);
437
438 // Ok, done
439 return hICC;
440
441 Error:
442 if (LUT != NULL)
443 cmsPipelineFree(LUT);
444
445 if (hICC != NULL)
446 cmsCloseProfile(hICC);
447
448 return NULL;
449 }
450
cmsCreateInkLimitingDeviceLink(cmsColorSpaceSignature ColorSpace,cmsFloat64Number Limit)451 cmsHPROFILE CMSEXPORT cmsCreateInkLimitingDeviceLink(cmsColorSpaceSignature ColorSpace, cmsFloat64Number Limit)
452 {
453 return cmsCreateInkLimitingDeviceLinkTHR(NULL, ColorSpace, Limit);
454 }
455
456
457 // Creates a fake Lab identity.
cmsCreateLab2ProfileTHR(cmsContext ContextID,const cmsCIExyY * WhitePoint)458 cmsHPROFILE CMSEXPORT cmsCreateLab2ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
459 {
460 cmsHPROFILE hProfile;
461 cmsPipeline* LUT = NULL;
462
463 hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
464 if (hProfile == NULL) return NULL;
465
466 cmsSetProfileVersion(hProfile, 2.1);
467
468 cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
469 cmsSetColorSpace(hProfile, cmsSigLabData);
470 cmsSetPCS(hProfile, cmsSigLabData);
471
472 if (!SetTextTags(hProfile, L"Lab identity built-in")) return NULL;
473
474 // An identity LUT is all we need
475 LUT = cmsPipelineAlloc(ContextID, 3, 3);
476 if (LUT == NULL) goto Error;
477
478 cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3));
479
480 if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
481 cmsPipelineFree(LUT);
482
483 return hProfile;
484
485 Error:
486
487 if (LUT != NULL)
488 cmsPipelineFree(LUT);
489
490 if (hProfile != NULL)
491 cmsCloseProfile(hProfile);
492
493 return NULL;
494 }
495
496
cmsCreateLab2Profile(const cmsCIExyY * WhitePoint)497 cmsHPROFILE CMSEXPORT cmsCreateLab2Profile(const cmsCIExyY* WhitePoint)
498 {
499 return cmsCreateLab2ProfileTHR(NULL, WhitePoint);
500 }
501
502
503 // Creates a fake Lab V4 identity.
cmsCreateLab4ProfileTHR(cmsContext ContextID,const cmsCIExyY * WhitePoint)504 cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIExyY* WhitePoint)
505 {
506 cmsHPROFILE hProfile;
507 cmsPipeline* LUT = NULL;
508
509 hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
510 if (hProfile == NULL) return NULL;
511
512 cmsSetProfileVersion(hProfile, 4.2);
513
514 cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
515 cmsSetColorSpace(hProfile, cmsSigLabData);
516 cmsSetPCS(hProfile, cmsSigLabData);
517
518 if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
519
520 // An empty LUTs is all we need
521 LUT = cmsPipelineAlloc(ContextID, 3, 3);
522 if (LUT == NULL) goto Error;
523
524 cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3));
525
526 if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
527 cmsPipelineFree(LUT);
528
529 return hProfile;
530
531 Error:
532
533 if (LUT != NULL)
534 cmsPipelineFree(LUT);
535
536 if (hProfile != NULL)
537 cmsCloseProfile(hProfile);
538
539 return NULL;
540 }
541
cmsCreateLab4Profile(const cmsCIExyY * WhitePoint)542 cmsHPROFILE CMSEXPORT cmsCreateLab4Profile(const cmsCIExyY* WhitePoint)
543 {
544 return cmsCreateLab4ProfileTHR(NULL, WhitePoint);
545 }
546
547
548 // Creates a fake XYZ identity
cmsCreateXYZProfileTHR(cmsContext ContextID)549 cmsHPROFILE CMSEXPORT cmsCreateXYZProfileTHR(cmsContext ContextID)
550 {
551 cmsHPROFILE hProfile;
552 cmsPipeline* LUT = NULL;
553
554 hProfile = cmsCreateRGBProfileTHR(ContextID, cmsD50_xyY(), NULL, NULL);
555 if (hProfile == NULL) return NULL;
556
557 cmsSetProfileVersion(hProfile, 4.2);
558
559 cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
560 cmsSetColorSpace(hProfile, cmsSigXYZData);
561 cmsSetPCS(hProfile, cmsSigXYZData);
562
563 if (!SetTextTags(hProfile, L"XYZ identity built-in")) goto Error;
564
565 // An identity LUT is all we need
566 LUT = cmsPipelineAlloc(ContextID, 3, 3);
567 if (LUT == NULL) goto Error;
568
569 cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, 3));
570
571 if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
572 cmsPipelineFree(LUT);
573
574 return hProfile;
575
576 Error:
577
578 if (LUT != NULL)
579 cmsPipelineFree(LUT);
580
581 if (hProfile != NULL)
582 cmsCloseProfile(hProfile);
583
584 return NULL;
585 }
586
587
cmsCreateXYZProfile(void)588 cmsHPROFILE CMSEXPORT cmsCreateXYZProfile(void)
589 {
590 return cmsCreateXYZProfileTHR(NULL);
591 }
592
593
594 //sRGB Curves are defined by:
595 //
596 //If R�sRGB,G�sRGB, B�sRGB < 0.04045
597 //
598 // R = R�sRGB / 12.92
599 // G = G�sRGB / 12.92
600 // B = B�sRGB / 12.92
601 //
602 //
603 //else if R�sRGB,G�sRGB, B�sRGB >= 0.04045
604 //
605 // R = ((R�sRGB + 0.055) / 1.055)^2.4
606 // G = ((G�sRGB + 0.055) / 1.055)^2.4
607 // B = ((B�sRGB + 0.055) / 1.055)^2.4
608
609 static
Build_sRGBGamma(cmsContext ContextID)610 cmsToneCurve* Build_sRGBGamma(cmsContext ContextID)
611 {
612 cmsFloat64Number Parameters[5];
613
614 Parameters[0] = 2.4;
615 Parameters[1] = 1. / 1.055;
616 Parameters[2] = 0.055 / 1.055;
617 Parameters[3] = 1. / 12.92;
618 Parameters[4] = 0.04045;
619
620 return cmsBuildParametricToneCurve(ContextID, 4, Parameters);
621 }
622
623 // Create the ICC virtual profile for sRGB space
cmsCreate_sRGBProfileTHR(cmsContext ContextID)624 cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfileTHR(cmsContext ContextID)
625 {
626 cmsCIExyY D65;
627 cmsCIExyYTRIPLE Rec709Primaries = {
628 {0.6400, 0.3300, 1.0},
629 {0.3000, 0.6000, 1.0},
630 {0.1500, 0.0600, 1.0}
631 };
632 cmsToneCurve* Gamma22[3];
633 cmsHPROFILE hsRGB;
634
635 cmsWhitePointFromTemp(&D65, 6504);
636 Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma(ContextID);
637 if (Gamma22[0] == NULL) return NULL;
638
639 hsRGB = cmsCreateRGBProfileTHR(ContextID, &D65, &Rec709Primaries, Gamma22);
640 cmsFreeToneCurve(Gamma22[0]);
641 if (hsRGB == NULL) return NULL;
642
643 if (!SetTextTags(hsRGB, L"sRGB built-in")) {
644 cmsCloseProfile(hsRGB);
645 return NULL;
646 }
647
648 return hsRGB;
649 }
650
cmsCreate_sRGBProfile(void)651 cmsHPROFILE CMSEXPORT cmsCreate_sRGBProfile(void)
652 {
653 return cmsCreate_sRGBProfileTHR(NULL);
654 }
655
656
657
658 typedef struct {
659 cmsFloat64Number Brightness;
660 cmsFloat64Number Contrast;
661 cmsFloat64Number Hue;
662 cmsFloat64Number Saturation;
663 cmsCIEXYZ WPsrc, WPdest;
664
665 } BCHSWADJUSTS, *LPBCHSWADJUSTS;
666
667
668 static
bchswSampler(register const cmsUInt16Number In[],register cmsUInt16Number Out[],register void * Cargo)669 int bchswSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
670 {
671 cmsCIELab LabIn, LabOut;
672 cmsCIELCh LChIn, LChOut;
673 cmsCIEXYZ XYZ;
674 LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
675
676
677 cmsLabEncoded2Float(&LabIn, In);
678
679
680 cmsLab2LCh(&LChIn, &LabIn);
681
682 // Do some adjusts on LCh
683
684 LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
685 LChOut.C = LChIn.C + bchsw -> Saturation;
686 LChOut.h = LChIn.h + bchsw -> Hue;
687
688
689 cmsLCh2Lab(&LabOut, &LChOut);
690
691 // Move white point in Lab
692
693 cmsLab2XYZ(&bchsw ->WPsrc, &XYZ, &LabOut);
694 cmsXYZ2Lab(&bchsw ->WPdest, &LabOut, &XYZ);
695
696 // Back to encoded
697
698 cmsFloat2LabEncoded(Out, &LabOut);
699
700 return TRUE;
701 }
702
703
704 // Creates an abstract profile operating in Lab space for Brightness,
705 // contrast, Saturation and white point displacement
706
cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,int nLUTPoints,cmsFloat64Number Bright,cmsFloat64Number Contrast,cmsFloat64Number Hue,cmsFloat64Number Saturation,int TempSrc,int TempDest)707 cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
708 int nLUTPoints,
709 cmsFloat64Number Bright,
710 cmsFloat64Number Contrast,
711 cmsFloat64Number Hue,
712 cmsFloat64Number Saturation,
713 int TempSrc,
714 int TempDest)
715 {
716 cmsHPROFILE hICC;
717 cmsPipeline* Pipeline;
718 BCHSWADJUSTS bchsw;
719 cmsCIExyY WhitePnt;
720 cmsStage* CLUT;
721 cmsUInt32Number Dimensions[MAX_INPUT_DIMENSIONS];
722 int i;
723
724
725 bchsw.Brightness = Bright;
726 bchsw.Contrast = Contrast;
727 bchsw.Hue = Hue;
728 bchsw.Saturation = Saturation;
729
730 cmsWhitePointFromTemp(&WhitePnt, TempSrc );
731 cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt);
732
733 cmsWhitePointFromTemp(&WhitePnt, TempDest);
734 cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt);
735
736 hICC = cmsCreateProfilePlaceholder(ContextID);
737 if (!hICC) // can't allocate
738 return NULL;
739
740
741 cmsSetDeviceClass(hICC, cmsSigAbstractClass);
742 cmsSetColorSpace(hICC, cmsSigLabData);
743 cmsSetPCS(hICC, cmsSigLabData);
744
745 cmsSetHeaderRenderingIntent(hICC, INTENT_PERCEPTUAL);
746
747
748 // Creates a Pipeline with 3D grid only
749 Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
750 if (Pipeline == NULL) {
751 cmsCloseProfile(hICC);
752 return NULL;
753 }
754
755 for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints;
756 CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL);
757 if (CLUT == NULL) return NULL;
758
759
760 if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) {
761
762 // Shouldn't reach here
763 cmsPipelineFree(Pipeline);
764 cmsCloseProfile(hICC);
765 return NULL;
766 }
767
768 cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT);
769
770 // Create tags
771
772 if (!SetTextTags(hICC, L"BCHS built-in")) return NULL;
773
774 cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) cmsD50_XYZ());
775
776 cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline);
777
778 // Pipeline is already on virtual profile
779 cmsPipelineFree(Pipeline);
780
781 // Ok, done
782 return hICC;
783 }
784
785
cmsCreateBCHSWabstractProfile(int nLUTPoints,cmsFloat64Number Bright,cmsFloat64Number Contrast,cmsFloat64Number Hue,cmsFloat64Number Saturation,int TempSrc,int TempDest)786 CMSAPI cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints,
787 cmsFloat64Number Bright,
788 cmsFloat64Number Contrast,
789 cmsFloat64Number Hue,
790 cmsFloat64Number Saturation,
791 int TempSrc,
792 int TempDest)
793 {
794 return cmsCreateBCHSWabstractProfileTHR(NULL, nLUTPoints, Bright, Contrast, Hue, Saturation, TempSrc, TempDest);
795 }
796
797
798 // Creates a fake NULL profile. This profile return 1 channel as always 0.
799 // Is useful only for gamut checking tricks
cmsCreateNULLProfileTHR(cmsContext ContextID)800 cmsHPROFILE CMSEXPORT cmsCreateNULLProfileTHR(cmsContext ContextID)
801 {
802 cmsHPROFILE hProfile;
803 cmsPipeline* LUT = NULL;
804 cmsStage* PostLin;
805 cmsToneCurve* EmptyTab;
806 cmsUInt16Number Zero[2] = { 0, 0 };
807
808 hProfile = cmsCreateProfilePlaceholder(ContextID);
809 if (!hProfile) // can't allocate
810 return NULL;
811
812 cmsSetProfileVersion(hProfile, 4.2);
813
814 if (!SetTextTags(hProfile, L"NULL profile built-in")) goto Error;
815
816
817
818 cmsSetDeviceClass(hProfile, cmsSigOutputClass);
819 cmsSetColorSpace(hProfile, cmsSigGrayData);
820 cmsSetPCS(hProfile, cmsSigLabData);
821
822 // An empty LUTs is all we need
823 LUT = cmsPipelineAlloc(ContextID, 1, 1);
824 if (LUT == NULL) goto Error;
825
826 EmptyTab = cmsBuildTabulatedToneCurve16(ContextID, 2, Zero);
827 PostLin = cmsStageAllocToneCurves(ContextID, 1, &EmptyTab);
828 cmsFreeToneCurve(EmptyTab);
829
830 cmsPipelineInsertStage(LUT, cmsAT_END, PostLin);
831
832 if (!cmsWriteTag(hProfile, cmsSigBToA0Tag, (void*) LUT)) goto Error;
833 if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, cmsD50_XYZ())) goto Error;
834
835 cmsPipelineFree(LUT);
836 return hProfile;
837
838 Error:
839
840 if (LUT != NULL)
841 cmsPipelineFree(LUT);
842
843 if (hProfile != NULL)
844 cmsCloseProfile(hProfile);
845
846 return NULL;
847 }
848
cmsCreateNULLProfile(void)849 cmsHPROFILE CMSEXPORT cmsCreateNULLProfile(void)
850 {
851 return cmsCreateNULLProfileTHR(NULL);
852 }
853
854
855 static
IsPCS(cmsColorSpaceSignature ColorSpace)856 int IsPCS(cmsColorSpaceSignature ColorSpace)
857 {
858 return (ColorSpace == cmsSigXYZData ||
859 ColorSpace == cmsSigLabData);
860 }
861
862
863 static
FixColorSpaces(cmsHPROFILE hProfile,cmsColorSpaceSignature ColorSpace,cmsColorSpaceSignature PCS,cmsUInt32Number dwFlags)864 void FixColorSpaces(cmsHPROFILE hProfile,
865 cmsColorSpaceSignature ColorSpace,
866 cmsColorSpaceSignature PCS,
867 cmsUInt32Number dwFlags)
868 {
869 if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {
870
871 if (IsPCS(ColorSpace) && IsPCS(PCS)) {
872
873 cmsSetDeviceClass(hProfile, cmsSigAbstractClass);
874 cmsSetColorSpace(hProfile, ColorSpace);
875 cmsSetPCS(hProfile, PCS);
876 return;
877 }
878
879 if (IsPCS(ColorSpace) && !IsPCS(PCS)) {
880
881 cmsSetDeviceClass(hProfile, cmsSigOutputClass);
882 cmsSetPCS(hProfile, ColorSpace);
883 cmsSetColorSpace(hProfile, PCS);
884 return;
885 }
886
887 if (IsPCS(PCS) && !IsPCS(ColorSpace)) {
888
889 cmsSetDeviceClass(hProfile, cmsSigInputClass);
890 cmsSetColorSpace(hProfile, ColorSpace);
891 cmsSetPCS(hProfile, PCS);
892 return;
893 }
894 }
895
896 cmsSetDeviceClass(hProfile, cmsSigLinkClass);
897 cmsSetColorSpace(hProfile, ColorSpace);
898 cmsSetPCS(hProfile, PCS);
899 }
900
901
902
903 // This function creates a named color profile dumping all the contents of transform to a single profile
904 // In this way, LittleCMS may be used to "group" several named color databases into a single profile.
905 // It has, however, several minor limitations. PCS is always Lab, which is not very critic since this
906 // is the normal PCS for named color profiles.
907 static
CreateNamedColorDevicelink(cmsHTRANSFORM xform)908 cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
909 {
910 _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
911 cmsHPROFILE hICC = NULL;
912 int i, nColors;
913 cmsNAMEDCOLORLIST *nc2 = NULL, *Original = NULL;
914
915 // Create an empty placeholder
916 hICC = cmsCreateProfilePlaceholder(v->ContextID);
917 if (hICC == NULL) return NULL;
918
919 // Critical information
920 cmsSetDeviceClass(hICC, cmsSigNamedColorClass);
921 cmsSetColorSpace(hICC, v ->ExitColorSpace);
922 cmsSetPCS(hICC, cmsSigLabData);
923
924 // Tag profile with information
925 if (!SetTextTags(hICC, L"Named color devicelink")) goto Error;
926
927 Original = cmsGetNamedColorList(xform);
928 if (Original == NULL) goto Error;
929
930 nColors = cmsNamedColorCount(Original);
931 nc2 = cmsDupNamedColorList(Original);
932 if (nc2 == NULL) goto Error;
933
934 // Colorant count now depends on the output space
935 nc2 ->ColorantCount = cmsPipelineOutputChannels(v ->Lut);
936
937 // Apply the transfor to colorants.
938 for (i=0; i < nColors; i++) {
939 cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
940 }
941
942 if (!cmsWriteTag(hICC, cmsSigNamedColor2Tag, (void*) nc2)) goto Error;
943 cmsFreeNamedColorList(nc2);
944
945 return hICC;
946
947 Error:
948 if (hICC != NULL) cmsCloseProfile(hICC);
949 return NULL;
950 }
951
952
953 // This structure holds information about which MPU can be stored on a profile based on the version
954
955 typedef struct {
956 cmsBool IsV4; // Is a V4 tag?
957 cmsTagSignature RequiredTag; // Set to 0 for both types
958 cmsTagTypeSignature LutType; // The LUT type
959 int nTypes; // Number of types (up to 5)
960 cmsStageSignature MpeTypes[5]; // 5 is the maximum number
961
962 } cmsAllowedLUT;
963
964 static const cmsAllowedLUT AllowedLUTTypes[] = {
965
966 { FALSE, 0, cmsSigLut16Type, 4, { cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType}},
967 { FALSE, 0, cmsSigLut16Type, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType}},
968 { TRUE , 0, cmsSigLutAtoBType, 1, { cmsSigCurveSetElemType }},
969 { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType, 3, { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType } },
970 { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType } },
971 { TRUE , cmsSigAToB0Tag, cmsSigLutAtoBType, 5, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
972 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 1, { cmsSigCurveSetElemType }},
973 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 3, { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType }},
974 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 3, { cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }},
975 { TRUE , cmsSigBToA0Tag, cmsSigLutBtoAType, 5, { cmsSigCurveSetElemType, cmsSigMatrixElemType, cmsSigCurveSetElemType, cmsSigCLutElemType, cmsSigCurveSetElemType }}
976 };
977
978 #define SIZE_OF_ALLOWED_LUT (sizeof(AllowedLUTTypes)/sizeof(cmsAllowedLUT))
979
980 // Check a single entry
981 static
CheckOne(const cmsAllowedLUT * Tab,const cmsPipeline * Lut)982 cmsBool CheckOne(const cmsAllowedLUT* Tab, const cmsPipeline* Lut)
983 {
984 cmsStage* mpe;
985 int n;
986
987 for (n=0, mpe = Lut ->Elements; mpe != NULL; mpe = mpe ->Next, n++) {
988
989 if (n > Tab ->nTypes) return FALSE;
990 if (cmsStageType(mpe) != Tab ->MpeTypes[n]) return FALSE;
991 }
992
993 return (n == Tab ->nTypes);
994 }
995
996
997 static
FindCombination(const cmsPipeline * Lut,cmsBool IsV4,cmsTagSignature DestinationTag)998 const cmsAllowedLUT* FindCombination(const cmsPipeline* Lut, cmsBool IsV4, cmsTagSignature DestinationTag)
999 {
1000 int n;
1001
1002 for (n=0; n < SIZE_OF_ALLOWED_LUT; n++) {
1003
1004 const cmsAllowedLUT* Tab = AllowedLUTTypes + n;
1005
1006 if (IsV4 ^ Tab -> IsV4) continue;
1007 if ((Tab ->RequiredTag != 0) && (Tab ->RequiredTag != DestinationTag)) continue;
1008
1009 if (CheckOne(Tab, Lut)) return Tab;
1010 }
1011
1012 return NULL;
1013 }
1014
1015
1016 // Does convert a transform into a device link profile
cmsTransform2DeviceLink(cmsHTRANSFORM hTransform,cmsFloat64Number Version,cmsUInt32Number dwFlags)1017 cmsHPROFILE CMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, cmsFloat64Number Version, cmsUInt32Number dwFlags)
1018 {
1019 cmsHPROFILE hProfile = NULL;
1020 cmsUInt32Number FrmIn, FrmOut, ChansIn, ChansOut;
1021 cmsUInt32Number ColorSpaceBitsIn, ColorSpaceBitsOut;
1022 _cmsTRANSFORM* xform = (_cmsTRANSFORM*) hTransform;
1023 cmsPipeline* LUT = NULL;
1024 cmsStage* mpe;
1025 cmsContext ContextID = cmsGetTransformContextID(hTransform);
1026 const cmsAllowedLUT* AllowedLUT;
1027 cmsTagSignature DestinationTag;
1028
1029 _cmsAssert(hTransform != NULL);
1030
1031 // Get the first mpe to check for named color
1032 mpe = cmsPipelineGetPtrToFirstStage(xform ->Lut);
1033
1034 // Check if is a named color transform
1035 if (mpe != NULL) {
1036
1037 if (cmsStageType(mpe) == cmsSigNamedColorElemType) {
1038 return CreateNamedColorDevicelink(hTransform);
1039 }
1040 }
1041
1042 // First thing to do is to get a copy of the transformation
1043 LUT = cmsPipelineDup(xform ->Lut);
1044 if (LUT == NULL) return NULL;
1045
1046 // Time to fix the Lab2/Lab4 issue.
1047 if ((xform ->EntryColorSpace == cmsSigLabData) && (Version < 4.0)) {
1048
1049 cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocLabV2ToV4curves(ContextID));
1050 }
1051
1052 // On the output side too
1053 if ((xform ->ExitColorSpace) == cmsSigLabData && (Version < 4.0)) {
1054
1055 cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocLabV4ToV2(ContextID));
1056 }
1057
1058
1059 hProfile = cmsCreateProfilePlaceholder(ContextID);
1060 if (!hProfile) goto Error; // can't allocate
1061
1062 cmsSetProfileVersion(hProfile, Version);
1063
1064 FixColorSpaces(hProfile, xform -> EntryColorSpace, xform -> ExitColorSpace, dwFlags);
1065
1066 // Optimize the LUT and precalculate a devicelink
1067
1068 ChansIn = cmsChannelsOf(xform -> EntryColorSpace);
1069 ChansOut = cmsChannelsOf(xform -> ExitColorSpace);
1070
1071 ColorSpaceBitsIn = _cmsLCMScolorSpace(xform -> EntryColorSpace);
1072 ColorSpaceBitsOut = _cmsLCMScolorSpace(xform -> ExitColorSpace);
1073
1074 FrmIn = COLORSPACE_SH(ColorSpaceBitsIn) | CHANNELS_SH(ChansIn)|BYTES_SH(2);
1075 FrmOut = COLORSPACE_SH(ColorSpaceBitsOut) | CHANNELS_SH(ChansOut)|BYTES_SH(2);
1076
1077
1078 if (cmsGetDeviceClass(hProfile) == cmsSigOutputClass)
1079 DestinationTag = cmsSigBToA0Tag;
1080 else
1081 DestinationTag = cmsSigAToB0Tag;
1082
1083 // Check if the profile/version can store the result
1084 if (dwFlags & cmsFLAGS_FORCE_CLUT)
1085 AllowedLUT = NULL;
1086 else
1087 AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1088
1089 if (AllowedLUT == NULL) {
1090
1091 // Try to optimize
1092 _cmsOptimizePipeline(&LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1093 AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1094
1095 }
1096
1097 // If no way, then force CLUT that for sure can be written
1098 if (AllowedLUT == NULL) {
1099
1100 dwFlags |= cmsFLAGS_FORCE_CLUT;
1101 _cmsOptimizePipeline(&LUT, xform ->RenderingIntent, &FrmIn, &FrmOut, &dwFlags);
1102
1103 // Put identity curves if needed
1104 if (cmsPipelineStageCount(LUT) == 1) {
1105
1106 cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCurves(ContextID, ChansIn));
1107 cmsPipelineInsertStage(LUT, cmsAT_END, _cmsStageAllocIdentityCurves(ContextID, ChansOut));
1108 }
1109
1110 AllowedLUT = FindCombination(LUT, Version >= 4.0, DestinationTag);
1111 }
1112
1113 // Somethings is wrong...
1114 if (AllowedLUT == NULL) {
1115 goto Error;
1116 }
1117
1118
1119 if (dwFlags & cmsFLAGS_8BITS_DEVICELINK)
1120 cmsPipelineSetSaveAs8bitsFlag(LUT, TRUE);
1121
1122 // Tag profile with information
1123 if (!SetTextTags(hProfile, L"devicelink")) goto Error;
1124
1125 // Store result
1126 if (!cmsWriteTag(hProfile, DestinationTag, LUT)) goto Error;
1127
1128
1129 if (xform -> InputColorant != NULL) {
1130 if (!cmsWriteTag(hProfile, cmsSigColorantTableTag, xform->InputColorant)) goto Error;
1131 }
1132
1133 if (xform -> OutputColorant != NULL) {
1134 if (!cmsWriteTag(hProfile, cmsSigColorantTableOutTag, xform->OutputColorant)) goto Error;
1135 }
1136
1137 if (xform ->Sequence != NULL) {
1138 if (!_cmsWriteProfileSequence(hProfile, xform ->Sequence)) goto Error;
1139 }
1140
1141 cmsPipelineFree(LUT);
1142 return hProfile;
1143
1144 Error:
1145 if (LUT != NULL) cmsPipelineFree(LUT);
1146 cmsCloseProfile(hProfile);
1147 return NULL;
1148 }
1149