1 /* Copyright (C) 2001-2019 Artifex Software, Inc.
2    All Rights Reserved.
3 
4    This software is provided AS-IS with no warranty, either express or
5    implied.
6 
7    This software is distributed under license and may not be copied,
8    modified or distributed except as expressly authorized under the terms
9    of the license contained in the file LICENSE in this distribution.
10 
11    Refer to licensing information at http://www.artifex.com or contact
12    Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
13    CA 94945, U.S.A., +1(415)492-9861, for further information.
14 */
15 
16 
17 /* gsicc interface to LittleCMS2-MT */
18 
19 #include "memory_.h"
20 #include "lcms2mt.h"
21 #include "lcms2mt_plugin.h"
22 #include "gslibctx.h"
23 #include "gserrors.h"
24 #include "gp.h"
25 #include "gsicc_cms.h"
26 #include "gxdevice.h"
27 
28 #ifdef WITH_CAL
29 #include "cal.h"
30 #endif
31 
32 #define USE_LCMS2_LOCKING
33 
34 #ifdef USE_LCMS2_LOCKING
35 #include "gxsync.h"
36 #endif
37 
38 #define DUMP_CMS_BUFFER 0
39 #define DEBUG_LCMS_MEM 0
40 #define LCMS_BYTES_MASK T_BYTES(-1)	/* leaves only mask for the BYTES (currently 7) */
41 #define LCMS_ENDIAN16_MASK T_ENDIAN16(-1) /* similarly, for ENDIAN16 bit */
42 
43 #define gsicc_link_flags(hasalpha, planarIN, planarOUT, bigendianIN, bigendianOUT, bytesIN, bytesOUT) \
44     ((hasalpha != 0) << 2 | \
45      (planarIN != 0) << 5 | (planarOUT != 0) << 4 | \
46      (bigendianIN != 0) << 3 | (bigendianOUT != 0) << 2 | \
47      (bytesIN == 1) << 1 | (bytesOUT == 1))
48 
49 typedef struct gsicc_lcms2mt_link_list_s {
50     int flags;
51     cmsHTRANSFORM *hTransform;
52     struct gsicc_lcms2mt_link_list_s *next;
53 } gsicc_lcms2mt_link_list_t;
54 
55 /* Only provide warning about issues in lcms if debug build */
56 static void
gscms_error(cmsContext ContextID,cmsUInt32Number error_code,const char * error_text)57 gscms_error(cmsContext       ContextID,
58             cmsUInt32Number  error_code,
59             const char      *error_text)
60 {
61 #ifdef DEBUG
62     gs_warn1("cmm error : %s",error_text);
63 #endif
64 }
65 
66 static
gs_lcms2_malloc(cmsContext id,unsigned int size)67 void *gs_lcms2_malloc(cmsContext id, unsigned int size)
68 {
69     void *ptr;
70     gs_memory_t *mem = (gs_memory_t *)cmsGetContextUserData(id);
71 
72 #if defined(SHARE_LCMS) && SHARE_LCMS==1
73     ptr = malloc(size);
74 #else
75     ptr = gs_alloc_bytes(mem, size, "lcms");
76 #endif
77 
78 #if DEBUG_LCMS_MEM
79     gs_warn2("lcms malloc (%d) at 0x%x",size,ptr);
80 #endif
81     return ptr;
82 }
83 
84 static
gs_lcms2_free(cmsContext id,void * ptr)85 void gs_lcms2_free(cmsContext id, void *ptr)
86 {
87     gs_memory_t *mem = (gs_memory_t *)cmsGetContextUserData(id);
88     if (ptr != NULL) {
89 #if DEBUG_LCMS_MEM
90         gs_warn1("lcms free at 0x%x",ptr);
91 #endif
92 
93 #if defined(SHARE_LCMS) && SHARE_LCMS==1
94         free(ptr);
95 #else
96         gs_free_object(mem, ptr, "lcms");
97 #endif
98     }
99 }
100 
101 static
gs_lcms2_realloc(cmsContext id,void * ptr,unsigned int size)102 void *gs_lcms2_realloc(cmsContext id, void *ptr, unsigned int size)
103 {
104     gs_memory_t *mem = (gs_memory_t *)cmsGetContextUserData(id);
105     void *ptr2;
106 
107     if (ptr == 0)
108         return gs_lcms2_malloc(id, size);
109     if (size == 0)
110     {
111         gs_lcms2_free(id, ptr);
112         return NULL;
113     }
114 #if defined(SHARE_LCMS) && SHARE_LCMS==1
115     ptr2 = realloc(ptr, size);
116 #else
117     ptr2 = gs_resize_object(mem, ptr, size, "lcms");
118 #endif
119 
120 #if DEBUG_LCMS_MEM
121     gs_warn3("lcms realloc (%x,%d) at 0x%x",ptr,size,ptr2);
122 #endif
123     return ptr2;
124 }
125 
126 static cmsPluginMemHandler gs_cms_memhandler =
127 {
128     {
129         cmsPluginMagicNumber,
130         LCMS_VERSION,
131         cmsPluginMemHandlerSig,
132         NULL
133     },
134     gs_lcms2_malloc,
135     gs_lcms2_free,
136     gs_lcms2_realloc,
137     NULL,
138     NULL,
139     NULL,
140 };
141 
142 #ifdef USE_LCMS2_LOCKING
143 
144 static
gs_lcms2_createMutex(cmsContext id)145 void *gs_lcms2_createMutex(cmsContext id)
146 {
147     gs_memory_t *mem = (gs_memory_t *)cmsGetContextUserData(id);
148 
149     return gx_monitor_label(gx_monitor_alloc(mem), "lcms2");
150 }
151 
152 static
gs_lcms2_destroyMutex(cmsContext id,void * mtx)153 void gs_lcms2_destroyMutex(cmsContext id, void* mtx)
154 {
155     gx_monitor_free((gx_monitor_t *)mtx);
156 }
157 
158 static
gs_lcms2_lockMutex(cmsContext id,void * mtx)159 cmsBool gs_lcms2_lockMutex(cmsContext id, void* mtx)
160 {
161     return !gx_monitor_enter((gx_monitor_t *)mtx);
162 }
163 
164 static
gs_lcms2_unlockMutex(cmsContext id,void * mtx)165 void gs_lcms2_unlockMutex(cmsContext id, void* mtx)
166 {
167     gx_monitor_leave((gx_monitor_t *)mtx);
168 }
169 
170 static cmsPluginMutex gs_cms_mutexhandler =
171 {
172     {
173         cmsPluginMagicNumber,
174         LCMS_VERSION,
175         cmsPluginMutexSig,
176         NULL
177     },
178     gs_lcms2_createMutex,
179     gs_lcms2_destroyMutex,
180     gs_lcms2_lockMutex,
181     gs_lcms2_unlockMutex
182 };
183 
184 #endif
185 
186 static int
gscms_get_accuracy(gs_memory_t * mem)187 gscms_get_accuracy(gs_memory_t *mem)
188 {
189     gs_lib_ctx_t *ctx = gs_lib_ctx_get_interp_instance(mem);
190 
191     switch (ctx->icc_color_accuracy) {
192     case 0:
193         return cmsFLAGS_LOWRESPRECALC;
194     case 1:
195         return 0;
196     case 2:
197     default:
198         return cmsFLAGS_HIGHRESPRECALC;
199     }
200 }
201 
202 /* Get the number of channels for the profile.
203   Input count */
204 int
gscms_get_input_channel_count(gcmmhprofile_t profile,gs_memory_t * memory)205 gscms_get_input_channel_count(gcmmhprofile_t profile, gs_memory_t *memory)
206 {
207     cmsColorSpaceSignature colorspace;
208     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
209 
210     colorspace = cmsGetColorSpace(ctx, profile);
211     return cmsChannelsOf(ctx, colorspace);
212 }
213 
214 /* Get the number of output channels for the profile */
215 int
gscms_get_output_channel_count(gcmmhprofile_t profile,gs_memory_t * memory)216 gscms_get_output_channel_count(gcmmhprofile_t profile, gs_memory_t *memory)
217 {
218     cmsColorSpaceSignature colorspace;
219     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
220 
221     colorspace = cmsGetPCS(ctx, profile);
222     return cmsChannelsOf(ctx, colorspace);
223 }
224 
225 /* Get the number of colorant names in the clrt tag */
226 int
gscms_get_numberclrtnames(gcmmhprofile_t profile,gs_memory_t * memory)227 gscms_get_numberclrtnames(gcmmhprofile_t profile, gs_memory_t *memory)
228 {
229     cmsNAMEDCOLORLIST *lcms_names;
230     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
231 
232     lcms_names = (cmsNAMEDCOLORLIST *)cmsReadTag(ctx, profile,
233         cmsSigColorantTableTag);
234     return cmsNamedColorCount(ctx, lcms_names);
235 }
236 
237 /* Get the nth colorant name in the clrt tag */
238 char*
gscms_get_clrtname(gcmmhprofile_t profile,int colorcount,gs_memory_t * memory)239 gscms_get_clrtname(gcmmhprofile_t profile, int colorcount, gs_memory_t *memory)
240 {
241     cmsNAMEDCOLORLIST *lcms_names;
242     char name[256];
243     char *buf;
244     int length;
245     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
246 
247     lcms_names = (cmsNAMEDCOLORLIST *)cmsReadTag(ctx, profile,
248                                                  cmsSigColorantTableTag);
249     if (colorcount >= cmsNamedColorCount(ctx, lcms_names)) return(NULL);
250     if (cmsNamedColorInfo(ctx, lcms_names, colorcount, name, NULL, NULL, NULL,
251                           NULL) == 0)
252         return NULL;
253     length = strlen(name);
254     buf = (char*) gs_alloc_bytes(memory, length + 1, "gscms_get_clrtname");
255     if (buf)
256         strcpy(buf, name);
257     return buf;
258 }
259 
260 /* Check if the profile is a device link type */
261 bool
gscms_is_device_link(gcmmhprofile_t profile,gs_memory_t * memory)262 gscms_is_device_link(gcmmhprofile_t profile, gs_memory_t *memory)
263 {
264     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
265 
266     return cmsGetDeviceClass(ctx, profile) == cmsSigLinkClass;
267 }
268 
269 /* Needed for v2 profile creation */
270 int
gscms_get_device_class(gcmmhprofile_t profile,gs_memory_t * memory)271 gscms_get_device_class(gcmmhprofile_t profile, gs_memory_t *memory)
272 {
273     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
274 
275     return cmsGetDeviceClass(ctx, profile);
276 }
277 
278 /* Check if the profile is a input type */
279 bool
gscms_is_input(gcmmhprofile_t profile,gs_memory_t * memory)280 gscms_is_input(gcmmhprofile_t profile, gs_memory_t *memory)
281 {
282     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
283 
284     return cmsGetDeviceClass(ctx, profile) == cmsSigInputClass;
285 }
286 
287 /* Get the device space associated with this profile */
288 gsicc_colorbuffer_t
gscms_get_profile_data_space(gcmmhprofile_t profile,gs_memory_t * memory)289 gscms_get_profile_data_space(gcmmhprofile_t profile, gs_memory_t *memory)
290 {
291     cmsColorSpaceSignature colorspace;
292     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
293 
294     colorspace = cmsGetColorSpace(ctx, profile);
295     switch (colorspace) {
296         case cmsSigXYZData:
297             return gsCIEXYZ;
298         case cmsSigLabData:
299             return gsCIELAB;
300         case cmsSigRgbData:
301             return gsRGB;
302         case cmsSigGrayData:
303             return gsGRAY;
304         case cmsSigCmykData:
305             return gsCMYK;
306         default:
307             return gsNCHANNEL;
308     }
309 }
310 
311 /* Get ICC Profile handle from buffer */
312 gcmmhprofile_t
gscms_get_profile_handle_mem(unsigned char * buffer,unsigned int input_size,gs_memory_t * mem)313 gscms_get_profile_handle_mem(unsigned char *buffer, unsigned int input_size,
314     gs_memory_t *mem)
315 {
316     cmsContext ctx = gs_lib_ctx_get_cms_context(mem);
317 
318     cmsSetLogErrorHandler(ctx, gscms_error);
319     return cmsOpenProfileFromMem(ctx,buffer,input_size);
320 }
321 
322 /* Get ICC Profile handle from file ptr */
323 gcmmhprofile_t
gscms_get_profile_handle_file(const char * filename,gs_memory_t * mem)324 gscms_get_profile_handle_file(const char *filename, gs_memory_t *mem)
325 {
326     cmsContext ctx = gs_lib_ctx_get_cms_context(mem);
327 
328     return cmsOpenProfileFromFile(ctx, filename, "r");
329 }
330 
331 /* Transform an entire buffer */
332 int
gscms_transform_color_buffer(gx_device * dev,gsicc_link_t * icclink,gsicc_bufferdesc_t * input_buff_desc,gsicc_bufferdesc_t * output_buff_desc,void * inputbuffer,void * outputbuffer)333 gscms_transform_color_buffer(gx_device *dev, gsicc_link_t *icclink,
334                              gsicc_bufferdesc_t *input_buff_desc,
335                              gsicc_bufferdesc_t *output_buff_desc,
336                              void *inputbuffer, void *outputbuffer)
337 {
338     gsicc_lcms2mt_link_list_t *link_handle = (gsicc_lcms2mt_link_list_t *)(icclink->link_handle);
339     cmsHTRANSFORM hTransform = (cmsHTRANSFORM)link_handle->hTransform;
340     cmsUInt32Number dwInputFormat, dwOutputFormat, num_src_lcms, num_des_lcms;
341     int  hasalpha, planarIN, planarOUT, numbytesIN, numbytesOUT, big_endianIN, big_endianOUT;
342     int needed_flags = 0;
343     unsigned char *inputpos, *outputpos;
344     cmsContext ctx = gs_lib_ctx_get_cms_context(icclink->memory);
345 
346 #if DUMP_CMS_BUFFER
347     gp_file *fid_in, *fid_out;
348 #endif
349     /* Although little CMS does  make assumptions about data types in its
350        transformations we can change it after the fact by cloning from any
351        other transform. We always create [0] which is no_alpha, chunky IN/OUT,
352        little_endian IN/OUT, 2-bytes_per_component IN/OUT. */
353     /* Set us to the proper output type */
354     /* Note, we could speed this up by passing back the encoded data type
355         to the caller so that we could avoid having to go through this
356         computation each time if they are doing multiple calls to this
357         operation, but this is working on a buffer at a time. */
358 
359     /* Now set if we have planar, num bytes, endian case, and alpha data to skip */
360     /* Planar -- pdf14 case for example */
361     planarIN = input_buff_desc->is_planar;
362     planarOUT = output_buff_desc->is_planar;
363 
364     /* 8 or 16 byte input and output */
365     numbytesIN = input_buff_desc->bytes_per_chan;
366     numbytesOUT = output_buff_desc->bytes_per_chan;
367     if (numbytesIN > 2 || numbytesOUT > 2)
368         return_error(gs_error_rangecheck);	/* TODO: we don't support float */
369 
370     /* endian */
371     big_endianIN = !input_buff_desc->little_endian;
372     big_endianOUT = !output_buff_desc->little_endian;
373 
374     /* alpha, which is passed through unmolested */
375     /* TODO:  Right now we always must have alpha last */
376     /* This is really only going to be an issue when we have interleaved alpha data */
377     hasalpha = input_buff_desc->has_alpha;
378 
379     needed_flags = gsicc_link_flags(hasalpha, planarIN, planarOUT,
380                                     big_endianIN, big_endianOUT,
381                                     numbytesIN, numbytesOUT);
382     while (link_handle->flags != needed_flags) {
383         if (link_handle->next == NULL) {
384             hTransform = NULL;
385             break;
386         } else {
387             link_handle = link_handle->next;
388             hTransform = link_handle->hTransform;
389         }
390     }
391     if (hTransform == NULL) {
392         /* the variant we want wasn't present, clone it from the last on the list */
393         gsicc_lcms2mt_link_list_t *new_link_handle =
394             (gsicc_lcms2mt_link_list_t *)gs_alloc_bytes(icclink->memory->non_gc_memory,
395                                                          sizeof(gsicc_lcms2mt_link_list_t),
396                                                          "gscms_transform_color_buffer");
397         if (new_link_handle == NULL) {
398             return_error(gs_error_VMerror);
399         }
400         new_link_handle->next = NULL;		/* new end of list */
401         new_link_handle->flags = needed_flags;
402         hTransform = link_handle->hTransform;	/* doesn't really matter which we start with */
403         /* Color space MUST be the same */
404         dwInputFormat = COLORSPACE_SH(T_COLORSPACE(cmsGetTransformInputFormat(ctx, hTransform)));
405         dwOutputFormat = COLORSPACE_SH(T_COLORSPACE(cmsGetTransformOutputFormat(ctx, hTransform)));
406         /* number of channels.  This should not really be changing! */
407         num_src_lcms = T_CHANNELS(cmsGetTransformInputFormat(ctx, hTransform));
408         num_des_lcms = T_CHANNELS(cmsGetTransformOutputFormat(ctx, hTransform));
409         if (num_src_lcms != input_buff_desc->num_chan ||
410             num_des_lcms != output_buff_desc->num_chan) {
411             /* We can't transform this. Someone is doing something odd */
412             return_error(gs_error_unknownerror);
413         }
414         dwInputFormat = dwInputFormat | CHANNELS_SH(num_src_lcms);
415         dwOutputFormat = dwOutputFormat | CHANNELS_SH(num_des_lcms);
416         /* set the remaining parameters, alpha, planar, num_bytes */
417         dwInputFormat = dwInputFormat | EXTRA_SH(hasalpha);
418         dwOutputFormat = dwOutputFormat | EXTRA_SH(hasalpha);
419         dwInputFormat = dwInputFormat | PLANAR_SH(planarIN);
420         dwOutputFormat = dwOutputFormat | PLANAR_SH(planarOUT);
421         dwInputFormat = dwInputFormat | ENDIAN16_SH(big_endianIN);
422         dwOutputFormat = dwOutputFormat | ENDIAN16_SH(big_endianOUT);
423         dwInputFormat = dwInputFormat | BYTES_SH(numbytesIN);
424         dwOutputFormat = dwOutputFormat | BYTES_SH(numbytesOUT);
425 
426         hTransform = cmsCloneTransformChangingFormats(ctx, hTransform, dwInputFormat, dwOutputFormat);
427         if (hTransform == NULL)
428             return_error(gs_error_unknownerror);
429         /* Now we have a new hTransform to add to the list, BUT some other thread */
430         /* may have been working in the same one. Lock, check again and add to    */
431         /* the (potentially new) end of the list */
432         gx_monitor_enter(icclink->lock);
433         while (link_handle->next != NULL) {
434             if (link_handle->flags == needed_flags) {
435                 /* OOPS. Someone else did it while we were building it */
436                 cmsDeleteTransform(ctx, hTransform);
437                 hTransform = link_handle->hTransform;
438                 new_link_handle = NULL;
439                 break;
440             }
441             link_handle = link_handle->next;
442         }
443         gx_monitor_leave(icclink->lock);
444         if (new_link_handle != NULL) {
445             new_link_handle->hTransform = hTransform;
446             link_handle->next = new_link_handle;		/* link to end of list */
447         }
448     }
449 
450     inputpos = (byte *) inputbuffer;
451     outputpos = (byte *) outputbuffer;
452     cmsDoTransformLineStride(ctx, hTransform,
453         inputpos, outputpos, input_buff_desc->pixels_per_row,
454         input_buff_desc->num_rows, input_buff_desc->row_stride,
455         output_buff_desc->row_stride, input_buff_desc->plane_stride,
456         output_buff_desc->plane_stride);
457 
458 #if DUMP_CMS_BUFFER
459     fid_in = gp_fopen(icclink->memory,"CM_Input.raw","ab");
460     fid_out = gp_fopen(icclink->memory,"CM_Output.raw","ab");
461     fwrite((unsigned char*) inputbuffer,sizeof(unsigned char),
462                             input_buff_desc->row_stride,fid_in);
463     fwrite((unsigned char*) outputbuffer,sizeof(unsigned char),
464                             output_buff_desc->row_stride,fid_out);
465     fclose(fid_in);
466     fclose(fid_out);
467 #endif
468     return 0;
469 }
470 
471 /* Transform a single color. We assume we have passed to us the proper number
472    of elements of size gx_device_color. It is up to the caller to make sure
473    the proper allocations for the colors are there. */
474 int
gscms_transform_color(gx_device * dev,gsicc_link_t * icclink,void * inputcolor,void * outputcolor,int num_bytes)475 gscms_transform_color(gx_device *dev, gsicc_link_t *icclink, void *inputcolor,
476                              void *outputcolor, int num_bytes)
477 {
478     gsicc_lcms2mt_link_list_t *link_handle = (gsicc_lcms2mt_link_list_t *)(icclink->link_handle);
479     cmsHTRANSFORM hTransform = (cmsHTRANSFORM)link_handle->hTransform;
480     cmsUInt32Number dwInputFormat, dwOutputFormat;
481     cmsContext ctx = gs_lib_ctx_get_cms_context(icclink->memory);
482     int big_endianIN, big_endianOUT, needed_flags;
483 
484     /* For a single color, we are going to use the link as it is
485        with the exception of taking care of the word size. */
486     if (num_bytes > 2)
487         return_error(gs_error_rangecheck);	/* TODO: we don't support float */
488 
489     dwInputFormat = cmsGetTransformInputFormat(ctx, hTransform);
490     big_endianIN = T_ENDIAN16(dwInputFormat);
491     dwOutputFormat = cmsGetTransformOutputFormat(ctx, hTransform);
492     big_endianOUT = T_ENDIAN16(dwOutputFormat);
493 
494     needed_flags = gsicc_link_flags(0, 0, 0,	/* alpha and planar not used for single color */
495                                     big_endianIN, big_endianOUT,
496                                     num_bytes, num_bytes);
497     while (link_handle->flags != needed_flags) {
498         if (link_handle->next == NULL) {
499             hTransform = NULL;
500             break;
501         } else {
502             link_handle = link_handle->next;
503             hTransform = link_handle->hTransform;
504         }
505     }
506     if (hTransform == NULL) {
507         gsicc_lcms2mt_link_list_t *new_link_handle =
508             (gsicc_lcms2mt_link_list_t *)gs_alloc_bytes(icclink->memory->non_gc_memory,
509                                                          sizeof(gsicc_lcms2mt_link_list_t),
510                                                          "gscms_transform_color_buffer");
511         if (new_link_handle == NULL) {
512             return_error(gs_error_VMerror);
513         }
514         new_link_handle->next = NULL;		/* new end of list */
515         new_link_handle->flags = needed_flags;
516         hTransform = link_handle->hTransform;
517 
518         /* the variant we want wasn't present, clone it from the HEAD (no alpha, not planar) */
519         dwInputFormat = COLORSPACE_SH(T_COLORSPACE(cmsGetTransformInputFormat(ctx, hTransform)));
520         dwOutputFormat = COLORSPACE_SH(T_COLORSPACE(cmsGetTransformOutputFormat(ctx, hTransform)));
521         dwInputFormat = dwInputFormat | CHANNELS_SH(T_CHANNELS(cmsGetTransformInputFormat(ctx, hTransform)));
522         dwOutputFormat = dwOutputFormat | CHANNELS_SH(T_CHANNELS(cmsGetTransformOutputFormat(ctx, hTransform)));
523         dwInputFormat = dwInputFormat | ENDIAN16_SH(big_endianIN);
524         dwOutputFormat = dwOutputFormat | ENDIAN16_SH(big_endianOUT);
525         dwInputFormat = dwInputFormat | BYTES_SH(num_bytes);
526         dwOutputFormat = dwOutputFormat | BYTES_SH(num_bytes);
527 
528         /* Get the transform with the settings we need */
529         hTransform = cmsCloneTransformChangingFormats(ctx, hTransform, dwInputFormat, dwOutputFormat);
530 
531         if (hTransform == NULL)
532             return_error(gs_error_unknownerror);
533 
534         /* Now we have a new hTransform to add to the list, BUT some other thread */
535         /* may have been working in the same one. Lock, check again and add to    */
536         /* the (potentially new) end of the list */
537         gx_monitor_enter(icclink->lock);
538         while (link_handle->next != NULL) {
539             if (link_handle->flags == needed_flags) {
540                 /* OOPS. Someone else did it while we were building it */
541                 cmsDeleteTransform(ctx, hTransform);
542                 hTransform = link_handle->hTransform;
543                 new_link_handle = NULL;
544                 break;
545             }
546             link_handle = link_handle->next;
547         }
548         gx_monitor_leave(icclink->lock);
549         if (new_link_handle != NULL) {
550             new_link_handle->hTransform = hTransform;
551             link_handle->next = new_link_handle;		/* link to end of list */
552         }
553     }
554 
555     /* Do conversion */
556     cmsDoTransform(ctx, hTransform, inputcolor, outputcolor, 1);
557 
558     return 0;
559 }
560 
561 /* Get the flag to avoid having to the cmm do any white fix up, it such a flag
562    exists for the cmm */
563 int
gscms_avoid_white_fix_flag(gs_memory_t * memory)564 gscms_avoid_white_fix_flag(gs_memory_t *memory)
565 {
566     return cmsFLAGS_NOWHITEONWHITEFIXUP;
567 }
568 
569 void
gscms_get_link_dim(gcmmhlink_t link,int * num_inputs,int * num_outputs,gs_memory_t * memory)570 gscms_get_link_dim(gcmmhlink_t link, int *num_inputs, int *num_outputs,
571     gs_memory_t *memory)
572 {
573     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
574     gsicc_lcms2mt_link_list_t *link_handle = (gsicc_lcms2mt_link_list_t *)(link);
575     cmsHTRANSFORM hTransform = (cmsHTRANSFORM)link_handle->hTransform;
576 
577     *num_inputs = T_CHANNELS(cmsGetTransformInputFormat(ctx, hTransform));
578     *num_outputs = T_CHANNELS(cmsGetTransformOutputFormat(ctx, hTransform));
579 }
580 
581 /* Get the link from the CMS. TODO:  Add error checking */
582 gcmmhlink_t
gscms_get_link(gcmmhprofile_t lcms_srchandle,gcmmhprofile_t lcms_deshandle,gsicc_rendering_param_t * rendering_params,int cmm_flags,gs_memory_t * memory)583 gscms_get_link(gcmmhprofile_t  lcms_srchandle, gcmmhprofile_t lcms_deshandle,
584                gsicc_rendering_param_t *rendering_params, int cmm_flags,
585                gs_memory_t *memory)
586 {
587     cmsUInt32Number src_data_type,des_data_type;
588     cmsColorSpaceSignature src_color_space,des_color_space;
589     int src_nChannels,des_nChannels;
590     int lcms_src_color_space, lcms_des_color_space;
591     unsigned int flag;
592     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
593     gsicc_lcms2mt_link_list_t *link_handle;
594 
595     /* Check for case of request for a transfrom from a device link profile
596        in that case, the destination profile is NULL */
597 
598     /* First handle all the source stuff */
599     src_color_space  = cmsGetColorSpace(ctx, lcms_srchandle);
600     lcms_src_color_space = _cmsLCMScolorSpace(ctx, src_color_space);
601 
602     /* littlecms returns -1 for types it does not (but should) understand */
603     if (lcms_src_color_space < 0)
604         lcms_src_color_space = 0;
605     src_nChannels = cmsChannelsOf(ctx, src_color_space);
606 
607     /* For now, just do single byte data, interleaved.  We can change this
608       when we use the transformation. */
609     src_data_type = (COLORSPACE_SH(lcms_src_color_space)|
610                         CHANNELS_SH(src_nChannels)|BYTES_SH(2));
611 #if 0
612     src_data_type = src_data_type | ENDIAN16_SH(1);
613 #endif
614     if (lcms_deshandle != NULL) {
615         des_color_space  = cmsGetColorSpace(ctx, lcms_deshandle);
616     } else {
617         /* We must have a device link profile. Use it's PCS space. */
618         des_color_space = cmsGetPCS(ctx, lcms_srchandle);
619     }
620     lcms_des_color_space = _cmsLCMScolorSpace(ctx, des_color_space);
621     if (lcms_des_color_space < 0)
622         lcms_des_color_space = 0;
623     des_nChannels = cmsChannelsOf(ctx, des_color_space);
624     des_data_type = (COLORSPACE_SH(lcms_des_color_space)|
625                         CHANNELS_SH(des_nChannels)|BYTES_SH(2));
626     /* endian */
627 #if 0
628     des_data_type = des_data_type | ENDIAN16_SH(1);
629 #endif
630     /* Set up the flags */
631     flag = gscms_get_accuracy(memory);
632     if (rendering_params->black_point_comp == gsBLACKPTCOMP_ON
633         || rendering_params->black_point_comp == gsBLACKPTCOMP_ON_OR) {
634         flag = (flag | cmsFLAGS_BLACKPOINTCOMPENSATION);
635     }
636     if (rendering_params->preserve_black == gsBLACKPRESERVE_KONLY) {
637         switch (rendering_params->rendering_intent) {
638             case INTENT_PERCEPTUAL:
639                 rendering_params->rendering_intent = INTENT_PRESERVE_K_ONLY_PERCEPTUAL;
640                 break;
641             case INTENT_RELATIVE_COLORIMETRIC:
642                 rendering_params->rendering_intent = INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC;
643                 break;
644             case INTENT_SATURATION:
645                 rendering_params->rendering_intent = INTENT_PRESERVE_K_ONLY_SATURATION;
646                 break;
647             default:
648                 break;
649         }
650     }
651     if (rendering_params->preserve_black == gsBLACKPRESERVE_KPLANE) {
652         switch (rendering_params->rendering_intent) {
653             case INTENT_PERCEPTUAL:
654                 rendering_params->rendering_intent = INTENT_PRESERVE_K_PLANE_PERCEPTUAL;
655                 break;
656             case INTENT_RELATIVE_COLORIMETRIC:
657                 rendering_params->rendering_intent = INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC;
658                 break;
659             case INTENT_SATURATION:
660                 rendering_params->rendering_intent = INTENT_PRESERVE_K_PLANE_SATURATION;
661                 break;
662             default:
663                 break;
664         }
665     }
666 
667     /* Create the link */
668     link_handle = (gsicc_lcms2mt_link_list_t *)gs_alloc_bytes(memory->non_gc_memory,
669                                                          sizeof(gsicc_lcms2mt_link_list_t),
670                                                          "gscms_transform_color_buffer");
671     if (link_handle == NULL)
672         return NULL;
673     link_handle->hTransform = cmsCreateTransform(ctx, lcms_srchandle, src_data_type,
674                                                     lcms_deshandle, des_data_type,
675                                                     rendering_params->rendering_intent,
676                                                     flag | cmm_flags);
677     if (link_handle->hTransform == NULL) {
678         gs_free_object(memory, link_handle, "gscms_get_link");
679             return NULL;
680     }
681     link_handle->next = NULL;
682     link_handle->flags = gsicc_link_flags(0, 0, 0, 0, 0,    /* no alpha, not planar, little-endian */
683                                           sizeof(gx_color_value), sizeof(gx_color_value));
684     return link_handle;
685     /* cmsFLAGS_HIGHRESPRECALC)  cmsFLAGS_NOTPRECALC  cmsFLAGS_LOWRESPRECALC*/
686 }
687 
688 /* Get the link from the CMS, but include proofing and/or a device link
689    profile.  Note also, that the source may be a device link profile, in
690    which case we will not have a destination profile but could still have
691    a proof profile or an additional device link profile */
692 gcmmhlink_t
gscms_get_link_proof_devlink(gcmmhprofile_t lcms_srchandle,gcmmhprofile_t lcms_proofhandle,gcmmhprofile_t lcms_deshandle,gcmmhprofile_t lcms_devlinkhandle,gsicc_rendering_param_t * rendering_params,bool src_dev_link,int cmm_flags,gs_memory_t * memory)693 gscms_get_link_proof_devlink(gcmmhprofile_t lcms_srchandle,
694                              gcmmhprofile_t lcms_proofhandle,
695                              gcmmhprofile_t lcms_deshandle,
696                              gcmmhprofile_t lcms_devlinkhandle,
697                              gsicc_rendering_param_t *rendering_params,
698                              bool src_dev_link, int cmm_flags,
699                              gs_memory_t *memory)
700 {
701     cmsUInt32Number src_data_type,des_data_type;
702     cmsColorSpaceSignature src_color_space,des_color_space;
703     int src_nChannels,des_nChannels;
704     int lcms_src_color_space, lcms_des_color_space;
705     cmsHPROFILE hProfiles[5];
706     int nProfiles = 0;
707     unsigned int flag;
708     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
709     gsicc_lcms2mt_link_list_t *link_handle;
710 
711     link_handle = (gsicc_lcms2mt_link_list_t *)gs_alloc_bytes(memory->non_gc_memory,
712                                                          sizeof(gsicc_lcms2mt_link_list_t),
713                                                          "gscms_transform_color_buffer");
714     if (link_handle == NULL)
715          return NULL;
716     link_handle->next = NULL;
717     link_handle->flags = gsicc_link_flags(0, 0, 0, 0, 0,    /* no alpha, not planar, little-endian */
718                                           sizeof(gx_color_value), sizeof(gx_color_value));
719     /* Check if the rendering intent is something other than relative colorimetric
720        and if we have a proofing profile.  In this case we need to create the
721        combined profile a bit different.  LCMS does not allow us to use different
722        intents in the cmsCreateMultiprofileTransform transform.  Also, don't even
723        think about doing this if someone has snuck in a source based device link
724        profile into the mix */
725     if (lcms_proofhandle != NULL &&
726         rendering_params->rendering_intent != gsRELATIVECOLORIMETRIC &&
727         !src_dev_link) {
728 
729         /* First handle the source to proof profile with its particular intent as
730            a device link profile */
731         cmsHPROFILE src_to_proof;
732 
733         link_handle = gscms_get_link(lcms_srchandle, lcms_proofhandle,
734             rendering_params, cmm_flags, memory);
735         if (link_handle->hTransform == NULL) {
736             gs_free_object(memory, link_handle, "gscms_get_link_proof_devlink");
737             return NULL;
738         }
739 
740         /* Now mash that to a device link profile */
741         flag = gscms_get_accuracy(memory);
742         if (rendering_params->black_point_comp == gsBLACKPTCOMP_ON ||
743             rendering_params->black_point_comp == gsBLACKPTCOMP_ON_OR) {
744             flag = (flag | cmsFLAGS_BLACKPOINTCOMPENSATION);
745         }
746         src_to_proof = cmsTransform2DeviceLink(ctx, link_handle->hTransform, 3.4, flag);
747         cmsDeleteTransform(ctx, link_handle->hTransform);
748 
749         src_color_space  = cmsGetColorSpace(ctx, src_to_proof);
750         lcms_src_color_space = _cmsLCMScolorSpace(ctx, src_color_space);
751 
752         /* littlecms returns -1 for types it does not (but should) understand */
753         if (lcms_src_color_space < 0)
754             lcms_src_color_space = 0;
755         src_nChannels = cmsChannelsOf(ctx, src_color_space);
756 
757         /* For now, just do single byte data, interleaved.  We can change this
758           when we use the transformation. */
759         src_data_type = (COLORSPACE_SH(lcms_src_color_space)|
760                             CHANNELS_SH(src_nChannels)|BYTES_SH(2));
761         if (lcms_devlinkhandle == NULL) {
762             des_color_space = cmsGetColorSpace(ctx, lcms_deshandle);
763         } else {
764             des_color_space = cmsGetPCS(ctx, lcms_devlinkhandle);
765         }
766         lcms_des_color_space = _cmsLCMScolorSpace(ctx, des_color_space);
767         if (lcms_des_color_space < 0) lcms_des_color_space = 0;
768         des_nChannels = cmsChannelsOf(ctx, des_color_space);
769         des_data_type = (COLORSPACE_SH(lcms_des_color_space)|
770                             CHANNELS_SH(des_nChannels)|BYTES_SH(2));
771 
772         /* Now, we need to go back through the proofing profile, to the
773            destination and then to the device link profile if there was one. */
774         hProfiles[nProfiles++] = src_to_proof;  /* Src to proof with special intent */
775         hProfiles[nProfiles++] = lcms_proofhandle; /* Proof to CIELAB */
776         if (lcms_deshandle != NULL) {
777             hProfiles[nProfiles++] = lcms_deshandle;  /* Our destination */
778         }
779 
780         /* The output device link profile */
781         if (lcms_devlinkhandle != NULL) {
782             hProfiles[nProfiles++] = lcms_devlinkhandle;
783         }
784         flag = gscms_get_accuracy(memory);
785         if (rendering_params->black_point_comp == gsBLACKPTCOMP_ON
786             || rendering_params->black_point_comp == gsBLACKPTCOMP_ON_OR) {
787             flag = (flag | cmsFLAGS_BLACKPOINTCOMPENSATION);
788         }
789 
790         /* Use relative colorimetric here */
791         link_handle->hTransform = cmsCreateMultiprofileTransform(ctx,
792                     hProfiles, nProfiles, src_data_type, des_data_type,
793                     gsRELATIVECOLORIMETRIC, flag);
794         cmsCloseProfile(ctx, src_to_proof);
795     } else {
796        /* First handle all the source stuff */
797         src_color_space  = cmsGetColorSpace(ctx, lcms_srchandle);
798         lcms_src_color_space = _cmsLCMScolorSpace(ctx, src_color_space);
799 
800         /* littlecms returns -1 for types it does not (but should) understand */
801         if (lcms_src_color_space < 0)
802             lcms_src_color_space = 0;
803         src_nChannels = cmsChannelsOf(ctx, src_color_space);
804 
805         /* For now, just do single byte data, interleaved.  We can change this
806           when we use the transformation. */
807         src_data_type = (COLORSPACE_SH(lcms_src_color_space)|
808                             CHANNELS_SH(src_nChannels)|BYTES_SH(2));
809         if (lcms_devlinkhandle == NULL) {
810             if (src_dev_link) {
811                 des_color_space = cmsGetPCS(ctx, lcms_srchandle);
812             } else {
813                 des_color_space = cmsGetColorSpace(ctx, lcms_deshandle);
814             }
815         } else {
816             des_color_space = cmsGetPCS(ctx, lcms_devlinkhandle);
817         }
818         lcms_des_color_space = _cmsLCMScolorSpace(ctx, des_color_space);
819         if (lcms_des_color_space < 0) lcms_des_color_space = 0;
820         des_nChannels = cmsChannelsOf(ctx, des_color_space);
821         des_data_type = (COLORSPACE_SH(lcms_des_color_space)|
822                             CHANNELS_SH(des_nChannels)|BYTES_SH(2));
823 
824         /* lcms proofing transform has a clunky API and can't include the device
825            link profile if we have both. So use cmsCreateMultiprofileTransform
826            instead and round trip the proofing profile. */
827         hProfiles[nProfiles++] = lcms_srchandle;
828 
829         /* Note if source is device link, we cannot do any proofing */
830         if (lcms_proofhandle != NULL && !src_dev_link) {
831             hProfiles[nProfiles++] = lcms_proofhandle;
832             hProfiles[nProfiles++] = lcms_proofhandle;
833         }
834 
835         /* This should be NULL if we have a source device link */
836         if (lcms_deshandle != NULL) {
837             hProfiles[nProfiles++] = lcms_deshandle;
838         }
839 
840         /* Someone could have a device link at the output, giving us possibly two
841            device link profiles to smash together */
842         if (lcms_devlinkhandle != NULL) {
843             hProfiles[nProfiles++] = lcms_devlinkhandle;
844         }
845         flag = gscms_get_accuracy(memory);
846         if (rendering_params->black_point_comp == gsBLACKPTCOMP_ON
847             || rendering_params->black_point_comp == gsBLACKPTCOMP_ON_OR) {
848             flag = (flag | cmsFLAGS_BLACKPOINTCOMPENSATION);
849         }
850         link_handle->hTransform =  cmsCreateMultiprofileTransform(ctx,
851                                        hProfiles, nProfiles, src_data_type,
852                                        des_data_type, rendering_params->rendering_intent, flag);
853     }
854     if (link_handle->hTransform == NULL) {
855         gs_free_object(memory, link_handle, "gscms_get_link_proof_devlink");
856         return NULL;
857     }
858     return link_handle;
859 }
860 
861 /* Do any initialization if needed to the CMS */
862 int
gscms_create(gs_memory_t * memory)863 gscms_create(gs_memory_t *memory)
864 {
865     cmsContext ctx;
866 
867     /* Set our own error handling function */
868     ctx = cmsCreateContext((void *)&gs_cms_memhandler, memory);
869     if (ctx == NULL)
870         return_error(gs_error_VMerror);
871 
872 #ifdef USE_LCMS2_LOCKING
873     cmsPlugin(ctx, (void *)&gs_cms_mutexhandler);
874 #endif
875 
876 #ifdef WITH_CAL
877     cmsPlugin(ctx, cal_cms_extensions());
878     cmsPlugin(ctx, cal_cms_extensions2());
879 #endif
880 
881     cmsSetLogErrorHandler(ctx, gscms_error);
882     gs_lib_ctx_set_cms_context(memory, ctx);
883     return 0;
884 }
885 
886 /* Do any clean up when done with the CMS if needed */
887 void
gscms_destroy(gs_memory_t * memory)888 gscms_destroy(gs_memory_t *memory)
889 {
890     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
891     if (ctx == NULL)
892         return;
893 
894     cmsDeleteContext(ctx);
895     gs_lib_ctx_set_cms_context(memory, NULL);
896 }
897 
898 /* Have the CMS release the link */
899 void
gscms_release_link(gsicc_link_t * icclink)900 gscms_release_link(gsicc_link_t *icclink)
901 {
902     cmsContext ctx = gs_lib_ctx_get_cms_context(icclink->memory);
903     gsicc_lcms2mt_link_list_t *link_handle = (gsicc_lcms2mt_link_list_t *)(icclink->link_handle);
904 
905     while (link_handle != NULL) {
906         gsicc_lcms2mt_link_list_t *next_handle;
907         cmsDeleteTransform(ctx, link_handle->hTransform);
908         next_handle = link_handle->next;
909         gs_free_object(icclink->memory->non_gc_memory, link_handle, "gscms_release_link");
910         link_handle = next_handle;
911     }
912     icclink->link_handle = NULL;
913 }
914 
915 /* Have the CMS release the profile handle */
916 void
gscms_release_profile(void * profile,gs_memory_t * memory)917 gscms_release_profile(void *profile, gs_memory_t *memory)
918 {
919     cmsHPROFILE profile_handle;
920     cmsContext ctx = gs_lib_ctx_get_cms_context(memory);
921 
922     profile_handle = (cmsHPROFILE) profile;
923     cmsCloseProfile(ctx, profile_handle);
924 }
925 
926 /* Named color, color management */
927 /* Get a device value for the named color.  Since there exist named color
928    ICC profiles and littleCMS supports them, we will use
929    that format in this example.  However it should be noted
930    that this object need not be an ICC named color profile
931    but can be a proprietary type table. Some CMMs do not
932    support named color profiles.  In that case, or if
933    the named color is not found, the caller should use an alternate
934    tint transform or other method. If a proprietary
935    format (nonICC) is being used, the operators given below must
936    be implemented. In every case that I can imagine, this should be
937    straight forward.  Note that we allow the passage of a tint
938    value also.  Currently the ICC named color profile does not provide
939    tint related information, only a value for 100% coverage.
940    It is provided here for use in proprietary
941    methods, which may be able to provide the desired effect.  We will
942    at the current time apply a direct tint operation to the returned
943    device value.
944    Right now I don't see any reason to have the named color profile
945    ever return CIELAB. It will either return device values directly or
946    it will return values defined by the output device profile */
947 
948 int
gscms_transform_named_color(gsicc_link_t * icclink,float tint_value,const char * ColorName,gx_color_value device_values[])949 gscms_transform_named_color(gsicc_link_t *icclink,  float tint_value,
950                             const char* ColorName, gx_color_value device_values[])
951 {
952     gsicc_lcms2mt_link_list_t *link_handle = (gsicc_lcms2mt_link_list_t *)(icclink->link_handle);
953     cmsHTRANSFORM hTransform = (cmsHTRANSFORM)link_handle->hTransform;
954     unsigned short *deviceptr = device_values;
955     int index;
956     cmsContext ctx = gs_lib_ctx_get_cms_context(icclink->memory);
957 
958     /* Check if name is present in the profile */
959     /* If it is not return -1.  Caller will need to use an alternate method */
960     if((index = cmsNamedColorIndex(ctx, hTransform, ColorName)) < 0)
961         return -1;
962 
963     /* Get the device value. */
964 /* FIXME: This looks WRONG. Doesn't it need to use tint_value??? */
965 /*        Also, the hTransform from link_handle is 2-byte IN, *NOT* float */
966    cmsDoTransform(ctx, hTransform,&index,deviceptr,1);
967    return 0;
968 }
969 
970 /* Create a link to return device values inside the named color profile or link
971    it with a destination profile and potentially a proofing profile.  If the
972    output_colorspace and the proof_color space are NULL, then we will be
973    returning the device values that are contained in the named color profile.
974    i.e. in namedcolor_information.  Note that an ICC named color profile
975     need NOT contain the device values but must contain the CIELAB values. */
976 void
gscms_get_name2device_link(gsicc_link_t * icclink,gcmmhprofile_t lcms_srchandle,gcmmhprofile_t lcms_deshandle,gcmmhprofile_t lcms_proofhandle,gsicc_rendering_param_t * rendering_params)977 gscms_get_name2device_link(gsicc_link_t *icclink,
978                            gcmmhprofile_t  lcms_srchandle,
979                            gcmmhprofile_t lcms_deshandle,
980                            gcmmhprofile_t lcms_proofhandle,
981                            gsicc_rendering_param_t *rendering_params)
982 {
983     cmsHTRANSFORM hTransform, hTransformNew;
984     cmsUInt32Number dwOutputFormat;
985     cmsUInt32Number lcms_proof_flag;
986     int number_colors;
987     cmsContext ctx = gs_lib_ctx_get_cms_context(icclink->memory);
988     gsicc_lcms2mt_link_list_t *link_handle;
989 
990     icclink->link_handle = NULL;		/* in case of failure */
991     /* NOTE:  We need to add a test here to check that we even HAVE
992     device values in here and NOT just CIELAB values */
993     if ( lcms_proofhandle != NULL ){
994         lcms_proof_flag = cmsFLAGS_GAMUTCHECK | cmsFLAGS_SOFTPROOFING;
995     } else {
996         lcms_proof_flag = 0;
997     }
998 
999     /* Create the transform */
1000     /* ToDo:  Adjust rendering intent */
1001     hTransform = cmsCreateProofingTransform(ctx,
1002                                             lcms_srchandle, TYPE_NAMED_COLOR_INDEX,
1003                                             lcms_deshandle, TYPE_CMYK_8,
1004                                             lcms_proofhandle,INTENT_PERCEPTUAL,
1005                                             INTENT_ABSOLUTE_COLORIMETRIC,
1006                                             lcms_proof_flag);
1007     if (hTransform == NULL)
1008         return;					/* bail */
1009 
1010     /* In littleCMS there is no easy way to find out the size of the device
1011         space returned by the named color profile until after the transform is made.
1012         Hence we adjust our output format after creating the transform.  It is
1013         set to CMYK8 initially. */
1014     number_colors = cmsNamedColorCount(ctx, cmsGetNamedColorList(hTransform));
1015 
1016     /* NOTE: Output size of gx_color_value with no color space type check */
1017     dwOutputFormat =  (CHANNELS_SH(number_colors)|BYTES_SH(sizeof(gx_color_value)));
1018 
1019     /* Change the formatters */
1020     hTransformNew  = cmsCloneTransformChangingFormats(ctx, hTransform,TYPE_NAMED_COLOR_INDEX,dwOutputFormat);
1021     cmsDeleteTransform(ctx, hTransform);	/* release the original after cloning */
1022     if (hTransformNew == NULL)
1023         return;					/* bail */
1024     link_handle = (gsicc_lcms2mt_link_list_t *)gs_alloc_bytes(icclink->memory->non_gc_memory,
1025                                                          sizeof(gsicc_lcms2mt_link_list_t),
1026                                                          "gscms_transform_color_buffer");
1027     if (link_handle == NULL) {
1028         cmsDeleteTransform(ctx, hTransformNew);
1029         return;					/* bail */
1030     }
1031     link_handle->flags = gsicc_link_flags(0, 0, 0, 0, 0,    /* no alpha, not planar, little-endian */
1032                                           sizeof(gx_color_value), sizeof(gx_color_value));
1033     link_handle->hTransform = hTransformNew;
1034     link_handle->next = NULL;
1035     icclink->link_handle = link_handle;
1036 
1037     cmsCloseProfile(ctx, lcms_srchandle);
1038     if(lcms_deshandle)
1039         cmsCloseProfile(ctx, lcms_deshandle);
1040     if(lcms_proofhandle)
1041         cmsCloseProfile(ctx, lcms_proofhandle);
1042     return;
1043 }
1044 
1045 bool
gscms_is_threadsafe(void)1046 gscms_is_threadsafe(void)
1047 {
1048     return true;              /* threads work correctly */
1049 }
1050