1 /*
  2  * Copyright (c) 2007, 2024, 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     cmsUInt32Number dwFlags = 0;
192     if (T_EXTRA(inFormatter) > 0 && T_EXTRA(outFormatter) > 0) {
193         dwFlags |= cmsFLAGS_COPY_ALPHA;
194     }
195 
196     sTrans = cmsCreateMultiprofileTransform(iccArray, j,
197         inFormatter, outFormatter, renderingIntent, dwFlags);
198 
199     (*env)->ReleaseLongArrayElements(env, profileIDs, ids, 0);
200 
201     if (sTrans == NULL) {
202         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_createNativeTransform: "
203                                        "sTrans == NULL");
204         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
205             JNU_ThrowByName(env, "java/awt/color/CMMException",
206                             "Cannot get color transform");
207         }
208     } else {
209         Disposer_AddRecord(env, disposerRef, LCMS_freeTransform, ptr_to_jlong(sTrans));
210     }
211 
212     if (iccArray != &_iccArray[0]) {
213         free(iccArray);
214     }
215     return ptr_to_jlong(sTrans);
216 }
217 
218 
219 /*
220  * Class:     sun_java2d_cmm_lcms_LCMS
221  * Method:    loadProfileNative
222  * Signature: ([BLjava/lang/Object;)J
223  */
224 JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
225   (JNIEnv *env, jclass cls, jbyteArray data, jobject disposerRef)
226 {
227     jbyte* dataArray;
228     jint dataSize;
229     lcmsProfile_p sProf = NULL;
230     cmsHPROFILE pf;
231 
232     if (JNU_IsNull(env, data)) {
233         ThrowIllegalArgumentException(env, "Invalid profile data");
234         return 0L;
235     }
236 
237     dataArray = (*env)->GetByteArrayElements (env, data, 0);
238     if (dataArray == NULL) {
239         // An exception should have already been thrown.
240         return 0L;
241     }
242 
243     dataSize = (*env)->GetArrayLength (env, data);
244 
245     pf = cmsOpenProfileFromMem((const void *)dataArray,
246                                      (cmsUInt32Number) dataSize);
247 
248     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
249 
250     if (pf == NULL) {
251         ThrowIllegalArgumentException(env, "Invalid profile data");
252     } else {
253         /* Sanity check: try to save the profile in order
254          * to force basic validation.
255          */
256         cmsUInt32Number pfSize = 0;
257         if (!cmsSaveProfileToMem(pf, NULL, &pfSize) ||
258             pfSize < sizeof(cmsICCHeader))
259         {
260             ThrowIllegalArgumentException(env, "Invalid profile data");
261             cmsCloseProfile(pf);
262             pf = NULL;
263         }
264     }
265 
266     if (pf != NULL) {
267         // create profile holder
268         sProf = (lcmsProfile_p)malloc(sizeof(lcmsProfile_t));
269         if (sProf != NULL) {
270             // register the disposer record
271             sProf->pf = pf;
272             Disposer_AddRecord(env, disposerRef, LCMS_freeProfile, ptr_to_jlong(sProf));
273         } else {
274             cmsCloseProfile(pf);
275         }
276     }
277 
278     return ptr_to_jlong(sProf);
279 }
280 
281 /*
282  * Class:     sun_java2d_cmm_lcms_LCMS
283  * Method:    getProfileDataNative
284  * Signature: (J)[B
285  */
286 JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
287   (JNIEnv *env, jclass cls, jlong id)
288 {
289     lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
290     cmsUInt32Number pfSize = 0;
291 
292     // determine actual profile size
293     if (!cmsSaveProfileToMem(sProf->pf, NULL, &pfSize)) {
294         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
295             JNU_ThrowByName(env, "java/awt/color/CMMException",
296                             "Can not access specified profile.");
297         }
298         return NULL;
299     }
300 
301     jbyteArray data = (*env)->NewByteArray(env, pfSize);
302     if (data == NULL) {
303         // An exception should have already been thrown.
304         return NULL;
305     }
306 
307     jbyte* dataArray = (*env)->GetByteArrayElements(env, data, 0);
308     if (dataArray == NULL) {
309         // An exception should have already been thrown.
310         return NULL;
311     }
312 
313     cmsBool status = cmsSaveProfileToMem(sProf->pf, dataArray, &pfSize);
314 
315     (*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
316 
317     if (!status) {
318         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
319             JNU_ThrowByName(env, "java/awt/color/CMMException",
320                             "Can not access specified profile.");
321         }
322         return NULL;
323     }
324     return data;
325 }
326 
327 /* Get profile header info */
328 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize);
329 static cmsBool _setHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize);
330 static cmsHPROFILE _writeCookedTag(cmsHPROFILE pfTarget, cmsTagSignature sig, jbyte *pData, jint size);
331 
332 
333 /*
334  * Class:     sun_java2d_cmm_lcms_LCMS
335  * Method:    getTagNative
336  * Signature: (JI)[B
337  */
338 JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getTagNative
339   (JNIEnv *env, jclass cls, jlong id, jint tagSig)
340 {
341     lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
342     TagSignature_t sig;
343     cmsUInt32Number tagSize;
344 
345     jbyte* dataArray = NULL;
346     jbyteArray data = NULL;
347 
348     cmsUInt32Number bufSize;
349 
350     sig.j = tagSig;
351 
352     if (tagSig == SigHead) {
353         cmsBool status;
354 
355         // allocate java array
356         bufSize = sizeof(cmsICCHeader);
357         data = (*env)->NewByteArray(env, bufSize);
358 
359         if (data == NULL) {
360             // An exception should have already been thrown.
361             return NULL;
362         }
363 
364         dataArray = (*env)->GetByteArrayElements (env, data, 0);
365 
366         if (dataArray == NULL) {
367             // An exception should have already been thrown.
368             return NULL;
369         }
370 
371         status = _getHeaderInfo(sProf->pf, dataArray, bufSize);
372 
373         (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
374 
375         if (!status) {
376             if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
377                 JNU_ThrowByName(env, "java/awt/color/CMMException",
378                                 "ICC Profile header not found");
379             }
380             return NULL;
381         }
382 
383         return data;
384     }
385 
386     if (cmsIsTag(sProf->pf, sig.cms)) {
387         tagSize = cmsReadRawTag(sProf->pf, sig.cms, NULL, 0);
388     } else {
389         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
390             JNU_ThrowByName(env, "java/awt/color/CMMException",
391                             "ICC profile tag not found");
392         }
393         return NULL;
394     }
395 
396     // allocate java array
397     data = (*env)->NewByteArray(env, tagSize);
398     if (data == NULL) {
399         // An exception should have already been thrown.
400         return NULL;
401     }
402 
403     dataArray = (*env)->GetByteArrayElements (env, data, 0);
404 
405     if (dataArray == NULL) {
406         // An exception should have already been thrown.
407         return NULL;
408     }
409 
410     bufSize = cmsReadRawTag(sProf->pf, sig.cms, dataArray, tagSize);
411 
412     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
413 
414     if (bufSize != tagSize) {
415         if (!(*env)->ExceptionCheck(env)) { // errorHandler may throw it
416             JNU_ThrowByName(env, "java/awt/color/CMMException",
417                             "Can not get tag data.");
418         }
419         return NULL;
420     }
421     return data;
422 }
423 
424 /*
425  * Class:     sun_java2d_cmm_lcms_LCMS
426  * Method:    setTagDataNative
427  * Signature: (JI[B)V
428  */
429 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagDataNative
430   (JNIEnv *env, jclass cls, jlong id, jint tagSig, jbyteArray data)
431 {
432     lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
433     cmsHPROFILE pfReplace = NULL;
434 
435     TagSignature_t sig;
436     cmsBool status = FALSE;
437     jbyte* dataArray;
438     int tagSize;
439 
440     sig.j = tagSig;
441 
442     if (JNU_IsNull(env, data)) {
443         ThrowIllegalArgumentException(env, "Can not write tag data.");
444         return;
445     }
446 
447     tagSize =(*env)->GetArrayLength(env, data);
448 
449     dataArray = (*env)->GetByteArrayElements(env, data, 0);
450 
451     if (dataArray == NULL) {
452         // An exception should have already been thrown.
453         return;
454     }
455 
456     if (tagSig == SigHead) {
457         status  = _setHeaderInfo(sProf->pf, dataArray, tagSize);
458     } else {
459         /*
460         * New strategy for generic tags: create a place holder,
461         * dump all existing tags there, dump externally supplied
462         * tag, and return the new profile to the java.
463         */
464         pfReplace = _writeCookedTag(sProf->pf, sig.cms, dataArray, tagSize);
465         status = (pfReplace != NULL);
466     }
467 
468     (*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
469 
470     if (!status) {
471         ThrowIllegalArgumentException(env, "Can not write tag data.");
472     } else if (pfReplace != NULL) {
473         cmsCloseProfile(sProf->pf);
474         sProf->pf = pfReplace;
475     }
476 }
477 
478 static void *getILData(JNIEnv *env, jobject data, jint type) {
479     switch (type) {
480         case DT_BYTE:
481             return (*env)->GetByteArrayElements(env, data, 0);
482         case DT_SHORT:
483             return (*env)->GetShortArrayElements(env, data, 0);
484         case DT_INT:
485             return (*env)->GetIntArrayElements(env, data, 0);
486         default:
487             return NULL;
488     }
489 }
490 
491 static void releaseILData(JNIEnv *env, void *pData, jint type, jobject data,
492                           jint mode) {
493     switch (type) {
494         case DT_BYTE:
495             (*env)->ReleaseByteArrayElements(env, data, (jbyte *) pData, mode);
496             break;
497         case DT_SHORT:
498             (*env)->ReleaseShortArrayElements(env, data, (jshort *) pData, mode);
499             break;
500         case DT_INT:
501             (*env)->ReleaseIntArrayElements(env, data, (jint *) pData, mode);
502             break;
503     }
504 }
505 
506 /*
507  * Class:     sun_java2d_cmm_lcms_LCMS
508  * Method:    colorConvert
509  * Signature: (JIIIIIIZZLjava/lang/Object;Ljava/lang/Object;)V
510  */
511 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
512   (JNIEnv *env, jclass cls, jlong ID, jint width, jint height, jint srcOffset,
513    jint srcNextRowOffset, jint dstOffset, jint dstNextRowOffset,
514    jobject srcData, jobject dstData, jint srcDType, jint dstDType)
515 {
516     cmsHTRANSFORM sTrans = jlong_to_ptr(ID);
517 
518     if (sTrans == NULL) {
519         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_colorConvert: transform == NULL");
520         JNU_ThrowByName(env, "java/awt/color/CMMException",
521                         "Cannot get color transform");
522         return;
523     }
524 
525     void *inputBuffer = getILData(env, srcData, srcDType);
526     if (inputBuffer == NULL) {
527         J2dRlsTraceLn(J2D_TRACE_ERROR, "");
528         // An exception should have already been thrown.
529         return;
530     }
531 
532     void *outputBuffer = getILData(env, dstData, dstDType);
533     if (outputBuffer == NULL) {
534         releaseILData(env, inputBuffer, srcDType, srcData, JNI_ABORT);
535         // An exception should have already been thrown.
536         return;
537     }
538 
539     char *input = (char *) inputBuffer + srcOffset;
540     char *output = (char *) outputBuffer + dstOffset;
541 
542     cmsDoTransformLineStride(sTrans, input, output, width, height,
543                              srcNextRowOffset, dstNextRowOffset, 0, 0);
544 
545     releaseILData(env, inputBuffer, srcDType, srcData, JNI_ABORT);
546     releaseILData(env, outputBuffer, dstDType, dstData, 0);
547 }
548 
549 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
550 {
551   cmsUInt32Number pfSize = 0;
552   cmsUInt8Number* pfBuffer = NULL;
553   cmsBool status = FALSE;
554 
555   if (!cmsSaveProfileToMem(pf, NULL, &pfSize) ||
556       pfSize < sizeof(cmsICCHeader) ||
557       bufferSize < (jint)sizeof(cmsICCHeader))
558   {
559     return FALSE;
560   }
561 
562   pfBuffer = malloc(pfSize);
563   if (pfBuffer == NULL) {
564     return FALSE;
565   }
566 
567   // load raw profile data into the buffer
568   if (cmsSaveProfileToMem(pf, pfBuffer, &pfSize)) {
569     memcpy(pBuffer, pfBuffer, sizeof(cmsICCHeader));
570     status = TRUE;
571   }
572   free(pfBuffer);
573   return status;
574 }
575 
576 static cmsBool _setHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
577 {
578   cmsICCHeader pfHeader;
579 
580   if (pBuffer == NULL || bufferSize < (jint)sizeof(cmsICCHeader)) {
581     return FALSE;
582   }
583 
584   memcpy(&pfHeader, pBuffer, sizeof(cmsICCHeader));
585 
586   // now set header fields, which we can access using the lcms2 public API
587   cmsSetHeaderFlags(pf, _cmsAdjustEndianess32(pfHeader.flags));
588   cmsSetHeaderManufacturer(pf, _cmsAdjustEndianess32(pfHeader.manufacturer));
589   cmsSetHeaderModel(pf, _cmsAdjustEndianess32(pfHeader.model));
590   cmsUInt64Number attributes;
591   _cmsAdjustEndianess64(&attributes, &pfHeader.attributes);
592   cmsSetHeaderAttributes(pf, attributes);
593   cmsSetHeaderProfileID(pf, (cmsUInt8Number*)&(pfHeader.profileID));
594   cmsSetHeaderRenderingIntent(pf, _cmsAdjustEndianess32(pfHeader.renderingIntent));
595   cmsSetPCS(pf, _cmsAdjustEndianess32(pfHeader.pcs));
596   cmsSetColorSpace(pf, _cmsAdjustEndianess32(pfHeader.colorSpace));
597   cmsSetDeviceClass(pf, _cmsAdjustEndianess32(pfHeader.deviceClass));
598   cmsSetEncodedICCversion(pf, _cmsAdjustEndianess32(pfHeader.version));
599 
600   return TRUE;
601 }
602 
603 /* Returns new profile handler, if it was created successfully,
604    NULL otherwise.
605    */
606 static cmsHPROFILE _writeCookedTag(const cmsHPROFILE pfTarget,
607                                const cmsTagSignature sig,
608                                jbyte *pData, jint size)
609 {
610     cmsUInt32Number pfSize = 0;
611     const cmsInt32Number tagCount = cmsGetTagCount(pfTarget);
612     cmsInt32Number i;
613     cmsHPROFILE pfSanity = NULL;
614 
615     cmsICCHeader hdr;
616 
617     cmsHPROFILE p = cmsCreateProfilePlaceholder(NULL);
618 
619     if (NULL == p) {
620         return NULL;
621     }
622     memset(&hdr, 0, sizeof(cmsICCHeader));
623 
624     // Populate the placeholder's header according to target profile
625     hdr.flags = cmsGetHeaderFlags(pfTarget);
626     hdr.renderingIntent = cmsGetHeaderRenderingIntent(pfTarget);
627     hdr.manufacturer = cmsGetHeaderManufacturer(pfTarget);
628     hdr.model = cmsGetHeaderModel(pfTarget);
629     hdr.pcs = cmsGetPCS(pfTarget);
630     hdr.colorSpace = cmsGetColorSpace(pfTarget);
631     hdr.deviceClass = cmsGetDeviceClass(pfTarget);
632     hdr.version = cmsGetEncodedICCversion(pfTarget);
633     cmsGetHeaderAttributes(pfTarget, &hdr.attributes);
634     cmsGetHeaderProfileID(pfTarget, (cmsUInt8Number*)&hdr.profileID);
635 
636     cmsSetHeaderFlags(p, hdr.flags);
637     cmsSetHeaderManufacturer(p, hdr.manufacturer);
638     cmsSetHeaderModel(p, hdr.model);
639     cmsSetHeaderAttributes(p, hdr.attributes);
640     cmsSetHeaderProfileID(p, (cmsUInt8Number*)&(hdr.profileID));
641     cmsSetHeaderRenderingIntent(p, hdr.renderingIntent);
642     cmsSetPCS(p, hdr.pcs);
643     cmsSetColorSpace(p, hdr.colorSpace);
644     cmsSetDeviceClass(p, hdr.deviceClass);
645     cmsSetEncodedICCversion(p, hdr.version);
646 
647     // now write the user supplied tag
648     if (size <= 0 || !cmsWriteRawTag(p, sig, pData, size)) {
649         cmsCloseProfile(p);
650         return NULL;
651     }
652 
653     // copy tags from the original profile
654     for (i = 0; i < tagCount; i++) {
655         cmsBool isTagReady = FALSE;
656         const cmsTagSignature s = cmsGetTagSignature(pfTarget, i);
657         const cmsUInt32Number tagSize = cmsReadRawTag(pfTarget, s, NULL, 0);
658 
659         if (s == sig) {
660             // skip the user supplied tag
661             continue;
662         }
663 
664         // read raw tag from the original profile
665         if (tagSize > 0) {
666             cmsUInt8Number* buf = (cmsUInt8Number*)malloc(tagSize);
667             if (buf != NULL) {
668                 if (tagSize ==  cmsReadRawTag(pfTarget, s, buf, tagSize)) {
669                     // now we are ready to write the tag
670                     isTagReady = cmsWriteRawTag(p, s, buf, tagSize);
671                 }
672                 free(buf);
673             }
674         }
675 
676         if (!isTagReady) {
677             cmsCloseProfile(p);
678             return NULL;
679         }
680     }
681 
682     // now we have all tags moved to the new profile.
683     // do some sanity checks: write it to a memory buffer and read again.
684     void* buf = NULL;
685     if (cmsSaveProfileToMem(p, NULL, &pfSize)) {
686         buf = malloc(pfSize);
687         if (buf != NULL) {
688             // load raw profile data into the buffer
689             if (cmsSaveProfileToMem(p, buf, &pfSize)) {
690                 pfSanity = cmsOpenProfileFromMem(buf, pfSize);
691             }
692         }
693     }
694 
695     cmsCloseProfile(p); // No longer needed.
696 
697     if (pfSanity == NULL) {
698         // for some reason, we failed to save and read the updated profile
699         // It likely indicates that the profile is not correct, so we report
700         // a failure here.
701         free(buf);
702         return NULL;
703     } else {
704         // do final check whether we can read and handle the target tag.
705         const void* pTag = cmsReadTag(pfSanity, sig);
706         if (pTag == NULL) {
707             // the tag can not be cooked
708             free(buf);
709             cmsCloseProfile(pfSanity);
710             return NULL;
711         }
712         // The profile we used for sanity checking needs to be returned
713         // since the one we updated is raw - not cooked.
714         // Except we want to re-open it since the call to cmsReadTag()
715         // means we may not get back the same bytes as we set.
716         // Whilst this may change later anyway, we can at least prevent
717         // it from happening immediately.
718         cmsCloseProfile(pfSanity);
719         pfSanity = cmsOpenProfileFromMem(buf, pfSize);
720         free(buf);
721         return pfSanity;
722     }
723 }