1 /*
  2  * Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 #include <stdio.h>
 27 #include <stdlib.h>
 28 #include <memory.h>
 29 #include "sun_java2d_cmm_lcms_LCMS.h"
 30 #include "sun_java2d_cmm_lcms_LCMSImageLayout.h"
 31 #include "jni_util.h"
 32 #include "Trace.h"
 33 #include "Disposer.h"
 34 #include <lcms2.h>
 35 #include <lcms2_plugin.h>
 36 #include "jlong.h"
 37 
 38 #define SigMake(a,b,c,d) \
 39                     ( ( ((int) ((unsigned char) (a))) << 24) | \
 40                       ( ((int) ((unsigned char) (b))) << 16) | \
 41                       ( ((int) ((unsigned char) (c))) <<  8) | \
 42                           (int) ((unsigned char) (d)))
 43 
 44 #define TagIdConst(a, b, c, d) \
 45                 ((int) SigMake ((a), (b), (c), (d)))
 46 
 47 #define SigHead TagIdConst('h','e','a','d')
 48 
 49 #define DT_BYTE     sun_java2d_cmm_lcms_LCMSImageLayout_DT_BYTE
 50 #define DT_SHORT    sun_java2d_cmm_lcms_LCMSImageLayout_DT_SHORT
 51 #define DT_INT      sun_java2d_cmm_lcms_LCMSImageLayout_DT_INT
 52 
 53 /* Default temp profile list size */
 54 #define DF_ICC_BUF_SIZE 32
 55 
 56 #define ERR_MSG_SIZE 256
 57 
 58 #ifdef _MSC_VER
 59 # ifndef snprintf
 60 #       define snprintf  _snprintf
 61 # endif
 62 #endif
 63 
 64 typedef struct lcmsProfile_s {
 65     cmsHPROFILE pf;
 66 } lcmsProfile_t, *lcmsProfile_p;
 67 
 68 typedef union {
 69     cmsTagSignature cms;
 70     jint j;
 71 } TagSignature_t, *TagSignature_p;
 72 
 73 JavaVM *javaVM;
 74 
 75 void errorHandler(cmsContext ContextID, cmsUInt32Number errorCode,
 76                   const char *errorText) {
 77     JNIEnv *env;
 78     char errMsg[ERR_MSG_SIZE];
 79 
 80     int count = snprintf(errMsg, ERR_MSG_SIZE,
 81                           "LCMS error %d: %s", errorCode, errorText);
 82     if (count < 0 || count >= ERR_MSG_SIZE) {
 83         count = ERR_MSG_SIZE - 1;
 84     }
 85     errMsg[count] = 0;
 86 
 87     (*javaVM)->AttachCurrentThread(javaVM, (void**)&env, NULL);
 88     if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it before
 89         JNU_ThrowByName(env, "java/awt/color/CMMException", errMsg);
 90     }
 91 }
 92 
 93 JNIEXPORT jint JNICALL DEF_JNI_OnLoad(JavaVM *jvm, void *reserved) {
 94     javaVM = jvm;
 95 
 96     cmsSetLogErrorHandler(errorHandler);
 97     return JNI_VERSION_1_6;
 98 }
 99 
100 void LCMS_freeProfile(JNIEnv *env, jlong ptr) {
101     lcmsProfile_p p = (lcmsProfile_p)jlong_to_ptr(ptr);
102 
103     if (p != NULL) {
104         if (p->pf != NULL) {
105             cmsCloseProfile(p->pf);
106         }
107         free(p);
108     }
109 }
110 
111 void LCMS_freeTransform(JNIEnv *env, jlong ID)
112 {
113     cmsHTRANSFORM sTrans = jlong_to_ptr(ID);
114     /* Passed ID is always valid native ref so there is no check for zero */
115     cmsDeleteTransform(sTrans);
116 }
117 
118 /*
119  * Throw an IllegalArgumentException and init the cause.
120  */
121 static void ThrowIllegalArgumentException(JNIEnv *env, const char *msg) {
122     jthrowable cause = (*env)->ExceptionOccurred(env);
123     if (cause != NULL) {
124         (*env)->ExceptionClear(env);
125     }
126     jstring str = JNU_NewStringPlatform(env, msg);
127     if (str != NULL) {
128         jobject iae = JNU_NewObjectByName(env,
129                                 "java/lang/IllegalArgumentException",
130                                 "(Ljava/lang/String;Ljava/lang/Throwable;)V",
131                                 str, cause);
132         if (iae != NULL) {
133             (*env)->Throw(env, iae);
134         }
135     }
136 }
137 
138 /*
139  * Class:     sun_java2d_cmm_lcms_LCMS
140  * Method:    createNativeTransform
141  * Signature: ([JIIZIZLjava/lang/Object;)J
142  */
143 JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_createNativeTransform
144   (JNIEnv *env, jclass cls, jlongArray profileIDs, jint renderingIntent,
145    jint inFormatter, jint outFormatter, jobject disposerRef)
146 {
147     cmsHPROFILE _iccArray[DF_ICC_BUF_SIZE];
148     cmsHPROFILE *iccArray = &_iccArray[0];
149     cmsHTRANSFORM sTrans = NULL;
150     int i, j, size;
151     jlong* ids;
152 
153     size = (*env)->GetArrayLength (env, profileIDs);
154     ids = (*env)->GetLongArrayElements(env, profileIDs, 0);
155     if (ids == NULL) {
156         // An exception should have already been thrown.
157         return 0L;
158     }
159 
160     if (DF_ICC_BUF_SIZE < size*2) {
161         iccArray = (cmsHPROFILE*) malloc(
162             size*2*sizeof(cmsHPROFILE));
163         if (iccArray == NULL) {
164             (*env)->ReleaseLongArrayElements(env, profileIDs, ids, 0);
165 
166             J2dRlsTraceLn(J2D_TRACE_ERROR, "getXForm: iccArray == NULL");
167             return 0L;
168         }
169     }
170 
171     j = 0;
172     for (i = 0; i < size; i++) {
173         cmsColorSpaceSignature cs;
174         lcmsProfile_p profilePtr = (lcmsProfile_p)jlong_to_ptr(ids[i]);
175         cmsHPROFILE icc = profilePtr->pf;
176 
177         iccArray[j++] = icc;
178 
179         /* Middle non-abstract profiles should be doubled before passing to
180          * the cmsCreateMultiprofileTransform function
181          */
182 
183         cs = cmsGetColorSpace(icc);
184         if (size > 2 && i != 0 && i != size - 1 &&
185             cs != cmsSigXYZData && cs != cmsSigLabData)
186         {
187             iccArray[j++] = icc;
188         }
189     }
190 
191     sTrans = cmsCreateMultiprofileTransform(iccArray, j,
192         inFormatter, outFormatter, renderingIntent, cmsFLAGS_COPY_ALPHA);
193 
194     (*env)->ReleaseLongArrayElements(env, profileIDs, ids, 0);
195 
196     if (sTrans == NULL) {
197         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_createNativeTransform: "
198                                        "sTrans == NULL");
199         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
200             JNU_ThrowByName(env, "java/awt/color/CMMException",
201                             "Cannot get color transform");
202         }
203     } else {
204         Disposer_AddRecord(env, disposerRef, LCMS_freeTransform, ptr_to_jlong(sTrans));
205     }
206 
207     if (iccArray != &_iccArray[0]) {
208         free(iccArray);
209     }
210     return ptr_to_jlong(sTrans);
211 }
212 
213 
214 /*
215  * Class:     sun_java2d_cmm_lcms_LCMS
216  * Method:    loadProfileNative
217  * Signature: ([BLjava/lang/Object;)J
218  */
219 JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
220   (JNIEnv *env, jclass cls, jbyteArray data, jobject disposerRef)
221 {
222     jbyte* dataArray;
223     jint dataSize;
224     lcmsProfile_p sProf = NULL;
225     cmsHPROFILE pf;
226 
227     if (JNU_IsNull(env, data)) {
228         ThrowIllegalArgumentException(env, "Invalid profile data");
229         return 0L;
230     }
231 
232     dataArray = (*env)->GetByteArrayElements (env, data, 0);
233     if (dataArray == NULL) {
234         // An exception should have already been thrown.
235         return 0L;
236     }
237 
238     dataSize = (*env)->GetArrayLength (env, data);
239 
240     pf = cmsOpenProfileFromMem((const void *)dataArray,
241                                      (cmsUInt32Number) dataSize);
242 
243     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
244 
245     if (pf == NULL) {
246         ThrowIllegalArgumentException(env, "Invalid profile data");
247     } else {
248         /* Sanity check: try to save the profile in order
249          * to force basic validation.
250          */
251         cmsUInt32Number pfSize = 0;
252         if (!cmsSaveProfileToMem(pf, NULL, &pfSize) ||
253             pfSize < sizeof(cmsICCHeader))
254         {
255             ThrowIllegalArgumentException(env, "Invalid profile data");
256             cmsCloseProfile(pf);
257             pf = NULL;
258         }
259     }
260 
261     if (pf != NULL) {
262         // create profile holder
263         sProf = (lcmsProfile_p)malloc(sizeof(lcmsProfile_t));
264         if (sProf != NULL) {
265             // register the disposer record
266             sProf->pf = pf;
267             Disposer_AddRecord(env, disposerRef, LCMS_freeProfile, ptr_to_jlong(sProf));
268         } else {
269             cmsCloseProfile(pf);
270         }
271     }
272 
273     return ptr_to_jlong(sProf);
274 }
275 
276 /*
277  * Class:     sun_java2d_cmm_lcms_LCMS
278  * Method:    getProfileDataNative
279  * Signature: (J)[B
280  */
281 JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
282   (JNIEnv *env, jclass cls, jlong id)
283 {
284     lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
285     cmsUInt32Number pfSize = 0;
286 
287     // determine actual profile size
288     if (!cmsSaveProfileToMem(sProf->pf, NULL, &pfSize)) {
289         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
290             JNU_ThrowByName(env, "java/awt/color/CMMException",
291                             "Can not access specified profile.");
292         }
293         return NULL;
294     }
295 
296     jbyteArray data = (*env)->NewByteArray(env, pfSize);
297     if (data == NULL) {
298         // An exception should have already been thrown.
299         return NULL;
300     }
301 
302     jbyte* dataArray = (*env)->GetByteArrayElements(env, data, 0);
303     if (dataArray == NULL) {
304         // An exception should have already been thrown.
305         return NULL;
306     }
307 
308     cmsBool status = cmsSaveProfileToMem(sProf->pf, dataArray, &pfSize);
309 
310     (*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
311 
312     if (!status) {
313         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
314             JNU_ThrowByName(env, "java/awt/color/CMMException",
315                             "Can not access specified profile.");
316         }
317         return NULL;
318     }
319     return data;
320 }
321 
322 /* Get profile header info */
323 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize);
324 static cmsBool _setHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize);
325 static cmsHPROFILE _writeCookedTag(cmsHPROFILE pfTarget, cmsTagSignature sig, jbyte *pData, jint size);
326 
327 
328 /*
329  * Class:     sun_java2d_cmm_lcms_LCMS
330  * Method:    getTagNative
331  * Signature: (JI)[B
332  */
333 JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getTagNative
334   (JNIEnv *env, jclass cls, jlong id, jint tagSig)
335 {
336     lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
337     TagSignature_t sig;
338     cmsUInt32Number tagSize;
339 
340     jbyte* dataArray = NULL;
341     jbyteArray data = NULL;
342 
343     cmsUInt32Number bufSize;
344 
345     sig.j = tagSig;
346 
347     if (tagSig == SigHead) {
348         cmsBool status;
349 
350         // allocate java array
351         bufSize = sizeof(cmsICCHeader);
352         data = (*env)->NewByteArray(env, bufSize);
353 
354         if (data == NULL) {
355             // An exception should have already been thrown.
356             return NULL;
357         }
358 
359         dataArray = (*env)->GetByteArrayElements (env, data, 0);
360 
361         if (dataArray == NULL) {
362             // An exception should have already been thrown.
363             return NULL;
364         }
365 
366         status = _getHeaderInfo(sProf->pf, dataArray, bufSize);
367 
368         (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
369 
370         if (!status) {
371             if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
372                 JNU_ThrowByName(env, "java/awt/color/CMMException",
373                                 "ICC Profile header not found");
374             }
375             return NULL;
376         }
377 
378         return data;
379     }
380 
381     if (cmsIsTag(sProf->pf, sig.cms)) {
382         tagSize = cmsReadRawTag(sProf->pf, sig.cms, NULL, 0);
383     } else {
384         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
385             JNU_ThrowByName(env, "java/awt/color/CMMException",
386                             "ICC profile tag not found");
387         }
388         return NULL;
389     }
390 
391     // allocate java array
392     data = (*env)->NewByteArray(env, tagSize);
393     if (data == NULL) {
394         // An exception should have already been thrown.
395         return NULL;
396     }
397 
398     dataArray = (*env)->GetByteArrayElements (env, data, 0);
399 
400     if (dataArray == NULL) {
401         // An exception should have already been thrown.
402         return NULL;
403     }
404 
405     bufSize = cmsReadRawTag(sProf->pf, sig.cms, dataArray, tagSize);
406 
407     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
408 
409     if (bufSize != tagSize) {
410         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
411             JNU_ThrowByName(env, "java/awt/color/CMMException",
412                             "Can not get tag data.");
413         }
414         return NULL;
415     }
416     return data;
417 }
418 
419 /*
420  * Class:     sun_java2d_cmm_lcms_LCMS
421  * Method:    setTagDataNative
422  * Signature: (JI[B)V
423  */
424 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagDataNative
425   (JNIEnv *env, jclass cls, jlong id, jint tagSig, jbyteArray data)
426 {
427     lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
428     cmsHPROFILE pfReplace = NULL;
429 
430     TagSignature_t sig;
431     cmsBool status = FALSE;
432     jbyte* dataArray;
433     int tagSize;
434 
435     sig.j = tagSig;
436 
437     if (JNU_IsNull(env, data)) {
438         ThrowIllegalArgumentException(env, "Can not write tag data.");
439         return;
440     }
441 
442     tagSize =(*env)->GetArrayLength(env, data);
443 
444     dataArray = (*env)->GetByteArrayElements(env, data, 0);
445 
446     if (dataArray == NULL) {
447         // An exception should have already been thrown.
448         return;
449     }
450 
451     if (tagSig == SigHead) {
452         status  = _setHeaderInfo(sProf->pf, dataArray, tagSize);
453     } else {
454         /*
455         * New strategy for generic tags: create a place holder,
456         * dump all existing tags there, dump externally supplied
457         * tag, and return the new profile to the java.
458         */
459         pfReplace = _writeCookedTag(sProf->pf, sig.cms, dataArray, tagSize);
460         status = (pfReplace != NULL);
461     }
462 
463     (*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
464 
465     if (!status) {
466         ThrowIllegalArgumentException(env, "Can not write tag data.");
467     } else if (pfReplace != NULL) {
468         cmsCloseProfile(sProf->pf);
469         sProf->pf = pfReplace;
470     }
471 }
472 
473 static void *getILData(JNIEnv *env, jobject data, jint type) {
474     switch (type) {
475         case DT_BYTE:
476             return (*env)->GetByteArrayElements(env, data, 0);
477         case DT_SHORT:
478             return (*env)->GetShortArrayElements(env, data, 0);
479         case DT_INT:
480             return (*env)->GetIntArrayElements(env, data, 0);
481         default:
482             return NULL;
483     }
484 }
485 
486 static void releaseILData(JNIEnv *env, void *pData, jint type, jobject data,
487                           jint mode) {
488     switch (type) {
489         case DT_BYTE:
490             (*env)->ReleaseByteArrayElements(env, data, (jbyte *) pData, mode);
491             break;
492         case DT_SHORT:
493             (*env)->ReleaseShortArrayElements(env, data, (jshort *) pData, mode);
494             break;
495         case DT_INT:
496             (*env)->ReleaseIntArrayElements(env, data, (jint *) pData, mode);
497             break;
498     }
499 }
500 
501 /*
502  * Class:     sun_java2d_cmm_lcms_LCMS
503  * Method:    colorConvert
504  * Signature: (JIIIIIIZZLjava/lang/Object;Ljava/lang/Object;)V
505  */
506 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
507   (JNIEnv *env, jclass cls, jlong ID, jint width, jint height, jint srcOffset,
508    jint srcNextRowOffset, jint dstOffset, jint dstNextRowOffset,
509    jobject srcData, jobject dstData, jint srcDType, jint dstDType)
510 {
511     cmsHTRANSFORM sTrans = jlong_to_ptr(ID);
512 
513     if (sTrans == NULL) {
514         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_colorConvert: transform == NULL");
515         JNU_ThrowByName(env, "java/awt/color/CMMException",
516                         "Cannot get color transform");
517         return;
518     }
519 
520     void *inputBuffer = getILData(env, srcData, srcDType);
521     if (inputBuffer == NULL) {
522         J2dRlsTraceLn(J2D_TRACE_ERROR, "");
523         // An exception should have already been thrown.
524         return;
525     }
526 
527     void *outputBuffer = getILData(env, dstData, dstDType);
528     if (outputBuffer == NULL) {
529         releaseILData(env, inputBuffer, srcDType, srcData, JNI_ABORT);
530         // An exception should have already been thrown.
531         return;
532     }
533 
534     char *input = (char *) inputBuffer + srcOffset;
535     char *output = (char *) outputBuffer + dstOffset;
536 
537     cmsDoTransformLineStride(sTrans, input, output, width, height,
538                              srcNextRowOffset, dstNextRowOffset, 0, 0);
539 
540     releaseILData(env, inputBuffer, srcDType, srcData, JNI_ABORT);
541     releaseILData(env, outputBuffer, dstDType, dstData, 0);
542 }
543 
544 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
545 {
546   cmsUInt32Number pfSize = 0;
547   cmsUInt8Number* pfBuffer = NULL;
548   cmsBool status = FALSE;
549 
550   if (!cmsSaveProfileToMem(pf, NULL, &pfSize) ||
551       pfSize < sizeof(cmsICCHeader) ||
552       bufferSize < (jint)sizeof(cmsICCHeader))
553   {
554     return FALSE;
555   }
556 
557   pfBuffer = malloc(pfSize);
558   if (pfBuffer == NULL) {
559     return FALSE;
560   }
561 
562   // load raw profile data into the buffer
563   if (cmsSaveProfileToMem(pf, pfBuffer, &pfSize)) {
564     memcpy(pBuffer, pfBuffer, sizeof(cmsICCHeader));
565     status = TRUE;
566   }
567   free(pfBuffer);
568   return status;
569 }
570 
571 static cmsBool _setHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
572 {
573   cmsICCHeader pfHeader;
574 
575   if (pBuffer == NULL || bufferSize < (jint)sizeof(cmsICCHeader)) {
576     return FALSE;
577   }
578 
579   memcpy(&pfHeader, pBuffer, sizeof(cmsICCHeader));
580 
581   // now set header fields, which we can access using the lcms2 public API
582   cmsSetHeaderFlags(pf, _cmsAdjustEndianess32(pfHeader.flags));
583   cmsSetHeaderManufacturer(pf, _cmsAdjustEndianess32(pfHeader.manufacturer));
584   cmsSetHeaderModel(pf, _cmsAdjustEndianess32(pfHeader.model));
585   cmsUInt64Number attributes;
586   _cmsAdjustEndianess64(&attributes, &pfHeader.attributes);
587   cmsSetHeaderAttributes(pf, attributes);
588   cmsSetHeaderProfileID(pf, (cmsUInt8Number*)&(pfHeader.profileID));
589   cmsSetHeaderRenderingIntent(pf, _cmsAdjustEndianess32(pfHeader.renderingIntent));
590   cmsSetPCS(pf, _cmsAdjustEndianess32(pfHeader.pcs));
591   cmsSetColorSpace(pf, _cmsAdjustEndianess32(pfHeader.colorSpace));
592   cmsSetDeviceClass(pf, _cmsAdjustEndianess32(pfHeader.deviceClass));
593   cmsSetEncodedICCversion(pf, _cmsAdjustEndianess32(pfHeader.version));
594 
595   return TRUE;
596 }
597 
598 /* Returns new profile handler, if it was created successfully,
599    NULL otherwise.
600    */
601 static cmsHPROFILE _writeCookedTag(const cmsHPROFILE pfTarget,
602                                const cmsTagSignature sig,
603                                jbyte *pData, jint size)
604 {
605     cmsUInt32Number pfSize = 0;
606     const cmsInt32Number tagCount = cmsGetTagCount(pfTarget);
607     cmsInt32Number i;
608     cmsHPROFILE pfSanity = NULL;
609 
610     cmsICCHeader hdr;
611 
612     cmsHPROFILE p = cmsCreateProfilePlaceholder(NULL);
613 
614     if (NULL == p) {
615         return NULL;
616     }
617     memset(&hdr, 0, sizeof(cmsICCHeader));
618 
619     // Populate the placeholder's header according to target profile
620     hdr.flags = cmsGetHeaderFlags(pfTarget);
621     hdr.renderingIntent = cmsGetHeaderRenderingIntent(pfTarget);
622     hdr.manufacturer = cmsGetHeaderManufacturer(pfTarget);
623     hdr.model = cmsGetHeaderModel(pfTarget);
624     hdr.pcs = cmsGetPCS(pfTarget);
625     hdr.colorSpace = cmsGetColorSpace(pfTarget);
626     hdr.deviceClass = cmsGetDeviceClass(pfTarget);
627     hdr.version = cmsGetEncodedICCversion(pfTarget);
628     cmsGetHeaderAttributes(pfTarget, &hdr.attributes);
629     cmsGetHeaderProfileID(pfTarget, (cmsUInt8Number*)&hdr.profileID);
630 
631     cmsSetHeaderFlags(p, hdr.flags);
632     cmsSetHeaderManufacturer(p, hdr.manufacturer);
633     cmsSetHeaderModel(p, hdr.model);
634     cmsSetHeaderAttributes(p, hdr.attributes);
635     cmsSetHeaderProfileID(p, (cmsUInt8Number*)&(hdr.profileID));
636     cmsSetHeaderRenderingIntent(p, hdr.renderingIntent);
637     cmsSetPCS(p, hdr.pcs);
638     cmsSetColorSpace(p, hdr.colorSpace);
639     cmsSetDeviceClass(p, hdr.deviceClass);
640     cmsSetEncodedICCversion(p, hdr.version);
641 
642     // now write the user supplied tag
643     if (size <= 0 || !cmsWriteRawTag(p, sig, pData, size)) {
644         cmsCloseProfile(p);
645         return NULL;
646     }
647 
648     // copy tags from the original profile
649     for (i = 0; i < tagCount; i++) {
650         cmsBool isTagReady = FALSE;
651         const cmsTagSignature s = cmsGetTagSignature(pfTarget, i);
652         const cmsUInt32Number tagSize = cmsReadRawTag(pfTarget, s, NULL, 0);
653 
654         if (s == sig) {
655             // skip the user supplied tag
656             continue;
657         }
658 
659         // read raw tag from the original profile
660         if (tagSize > 0) {
661             cmsUInt8Number* buf = (cmsUInt8Number*)malloc(tagSize);
662             if (buf != NULL) {
663                 if (tagSize ==  cmsReadRawTag(pfTarget, s, buf, tagSize)) {
664                     // now we are ready to write the tag
665                     isTagReady = cmsWriteRawTag(p, s, buf, tagSize);
666                 }
667                 free(buf);
668             }
669         }
670 
671         if (!isTagReady) {
672             cmsCloseProfile(p);
673             return NULL;
674         }
675     }
676 
677     // now we have all tags moved to the new profile.
678     // do some sanity checks: write it to a memory buffer and read again.
679     void* buf = NULL;
680     if (cmsSaveProfileToMem(p, NULL, &pfSize)) {
681         buf = malloc(pfSize);
682         if (buf != NULL) {
683             // load raw profile data into the buffer
684             if (cmsSaveProfileToMem(p, buf, &pfSize)) {
685                 pfSanity = cmsOpenProfileFromMem(buf, pfSize);
686             }
687         }
688     }
689 
690     cmsCloseProfile(p); // No longer needed.
691 
692     if (pfSanity == NULL) {
693         // for some reason, we failed to save and read the updated profile
694         // It likely indicates that the profile is not correct, so we report
695         // a failure here.
696         free(buf);
697         return NULL;
698     } else {
699         // do final check whether we can read and handle the target tag.
700         const void* pTag = cmsReadTag(pfSanity, sig);
701         if (pTag == NULL) {
702             // the tag can not be cooked
703             free(buf);
704             cmsCloseProfile(pfSanity);
705             return NULL;
706         }
707         // The profile we used for sanity checking needs to be returned
708         // since the one we updated is raw - not cooked.
709         // Except we want to re-open it since the call to cmsReadTag()
710         // means we may not get back the same bytes as we set.
711         // Whilst this may change later anyway, we can at least prevent
712         // it from happening immediately.
713         cmsCloseProfile(pfSanity);
714         pfSanity = cmsOpenProfileFromMem(buf, pfSize);
715         free(buf);
716         return pfSanity;
717     }
718 }