1 /*
   2  * Copyright (c) 2007, 2011, 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 "jni_util.h"
  31 #include "Trace.h"
  32 #include "Disposer.h"
  33 #include "lcms2.h"
  34 
  35 
  36 #define ALIGNLONG(x) (((x)+3) & ~(3))         // Aligns to DWORD boundary
  37 
  38 #ifdef USE_BIG_ENDIAN
  39 #define AdjustEndianess32(a)
  40 #else
  41 
  42 static
  43 void AdjustEndianess32(cmsUInt8Number *pByte)
  44 {
  45     cmsUInt8Number temp1;
  46     cmsUInt8Number temp2;
  47 
  48     temp1 = *pByte++;
  49     temp2 = *pByte++;
  50     *(pByte-1) = *pByte;
  51     *pByte++ = temp2;
  52     *(pByte-3) = *pByte;
  53     *pByte = temp1;
  54 }
  55 
  56 #endif
  57 
  58 // Transports to properly encoded values - note that icc profiles does use
  59 // big endian notation.
  60 
  61 static
  62 cmsInt32Number TransportValue32(cmsInt32Number Value)
  63 {
  64     cmsInt32Number Temp = Value;
  65 
  66     AdjustEndianess32((cmsUInt8Number*) &Temp);
  67     return Temp;
  68 }
  69 
  70 #define SigMake(a,b,c,d) \
  71                     ( ( ((int) ((unsigned char) (a))) << 24) | \
  72                       ( ((int) ((unsigned char) (b))) << 16) | \
  73                       ( ((int) ((unsigned char) (c))) <<  8) | \
  74                           (int) ((unsigned char) (d)))
  75 
  76 #define TagIdConst(a, b, c, d) \
  77                 ((int) SigMake ((a), (b), (c), (d)))
  78 
  79 #define SigHead TagIdConst('h','e','a','d')
  80 
  81 #define DT_BYTE     0
  82 #define DT_SHORT    1
  83 #define DT_INT      2
  84 #define DT_DOUBLE   3
  85 
  86 /* Default temp profile list size */
  87 #define DF_ICC_BUF_SIZE 32
  88 
  89 #define ERR_MSG_SIZE 256
  90 
  91 #ifdef _MSC_VER
  92 # ifndef snprintf
  93 #       define snprintf  _snprintf
  94 # endif
  95 #endif
  96 
  97 typedef struct lcmsProfile_s {
  98     cmsHPROFILE pf;
  99 } lcmsProfile_t, *lcmsProfile_p;
 100 
 101 typedef union storeID_s {    /* store SProfile stuff in a Java Long */
 102     lcmsProfile_p lcmsPf;
 103     cmsHTRANSFORM xf;
 104     jobject jobj;
 105     jlong j;
 106 } storeID_t, *storeID_p;
 107 
 108 typedef union {
 109     cmsTagSignature cms;
 110     jint j;
 111 } TagSignature_t, *TagSignature_p;
 112 
 113 static jfieldID Trans_renderType_fID;
 114 static jfieldID Trans_ID_fID;
 115 static jfieldID IL_isIntPacked_fID;
 116 static jfieldID IL_dataType_fID;
 117 static jfieldID IL_pixelType_fID;
 118 static jfieldID IL_dataArray_fID;
 119 static jfieldID IL_offset_fID;
 120 static jfieldID IL_nextRowOffset_fID;
 121 static jfieldID IL_width_fID;
 122 static jfieldID IL_height_fID;
 123 static jfieldID IL_imageAtOnce_fID;
 124 
 125 JavaVM *javaVM;
 126 
 127 void errorHandler(cmsContext ContextID, cmsUInt32Number errorCode,
 128                   const char *errorText) {
 129     JNIEnv *env;
 130     char errMsg[ERR_MSG_SIZE];
 131 
 132     int count = snprintf(errMsg, ERR_MSG_SIZE,
 133                           "LCMS error %d: %s", errorCode, errorText);
 134     if (count < 0 || count >= ERR_MSG_SIZE) {
 135         count = ERR_MSG_SIZE - 1;
 136     }
 137     errMsg[count] = 0;
 138 
 139     (*javaVM)->AttachCurrentThread(javaVM, (void**)&env, NULL);
 140     JNU_ThrowByName(env, "java/awt/color/CMMException", errMsg);
 141 }
 142 
 143 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
 144     javaVM = jvm;
 145 
 146     cmsSetLogErrorHandler(errorHandler);
 147     return JNI_VERSION_1_6;
 148 }
 149 
 150 void LCMS_freeProfile(JNIEnv *env, jlong ptr) {
 151     storeID_t sProfile;
 152     sProfile.j = ptr;
 153 
 154     if (sProfile.lcmsPf != NULL) {
 155         if (sProfile.lcmsPf->pf != NULL) {
 156             cmsCloseProfile(sProfile.lcmsPf->pf);
 157         }
 158         free(sProfile.lcmsPf);
 159     }
 160 }
 161 
 162 void LCMS_freeTransform(JNIEnv *env, jlong ID)
 163 {
 164     storeID_t sTrans;
 165     sTrans.j = ID;
 166     /* Passed ID is always valid native ref so there is no check for zero */
 167     cmsDeleteTransform(sTrans.xf);
 168 }
 169 
 170 /*
 171  * Class:     sun_java2d_cmm_lcms_LCMS
 172  * Method:    createNativeTransform
 173  * Signature: ([JI)J
 174  */
 175 JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_createNativeTransform
 176   (JNIEnv *env, jclass cls, jlongArray profileIDs, jint renderType,
 177    jint inFormatter, jboolean isInIntPacked,
 178    jint outFormatter, jboolean isOutIntPacked, jobject disposerRef)
 179 {
 180     cmsHPROFILE _iccArray[DF_ICC_BUF_SIZE];
 181     cmsHPROFILE *iccArray = &_iccArray[0];
 182     storeID_t sTrans;
 183     int i, j, size;
 184     jlong* ids;
 185 
 186     size = (*env)->GetArrayLength (env, profileIDs);
 187     ids = (*env)->GetLongArrayElements(env, profileIDs, 0);
 188 
 189 #ifdef _LITTLE_ENDIAN
 190     /* Reversing data packed into int for LE archs */
 191     if (isInIntPacked) {
 192         inFormatter ^= DOSWAP_SH(1);
 193     }
 194     if (isOutIntPacked) {
 195         outFormatter ^= DOSWAP_SH(1);
 196     }
 197 #endif
 198 
 199     if (DF_ICC_BUF_SIZE < size*2) {
 200         iccArray = (cmsHPROFILE*) malloc(
 201             size*2*sizeof(cmsHPROFILE));
 202         if (iccArray == NULL) {
 203             (*env)->ReleaseLongArrayElements(env, profileIDs, ids, 0);
 204 
 205             J2dRlsTraceLn(J2D_TRACE_ERROR, "getXForm: iccArray == NULL");
 206             return 0L;
 207         }
 208     }
 209 
 210     j = 0;
 211     for (i = 0; i < size; i++) {
 212         cmsHPROFILE icc;
 213         cmsColorSpaceSignature cs;
 214 
 215         sTrans.j = ids[i];
 216         icc = sTrans.lcmsPf->pf;
 217         iccArray[j++] = icc;
 218 
 219         /* Middle non-abstract profiles should be doubled before passing to
 220          * the cmsCreateMultiprofileTransform function
 221          */
 222 
 223         cs = cmsGetColorSpace(icc);
 224         if (size > 2 && i != 0 && i != size - 1 &&
 225             cs != cmsSigXYZData && cs != cmsSigLabData)
 226         {
 227             iccArray[j++] = icc;
 228         }
 229     }
 230 
 231     sTrans.xf = cmsCreateMultiprofileTransform(iccArray, j,
 232         inFormatter, outFormatter, renderType, 0);
 233 
 234     (*env)->ReleaseLongArrayElements(env, profileIDs, ids, 0);
 235 
 236     if (sTrans.xf == NULL) {
 237         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_createNativeTransform: "
 238                                        "sTrans.xf == NULL");
 239         if ((*env)->ExceptionOccurred(env) == NULL) {
 240             JNU_ThrowByName(env, "java/awt/color/CMMException",
 241                             "Cannot get color transform");
 242         }
 243     } else {
 244         Disposer_AddRecord(env, disposerRef, LCMS_freeTransform, sTrans.j);
 245     }
 246 
 247     if (iccArray != &_iccArray[0]) {
 248         free(iccArray);
 249     }
 250     return sTrans.j;
 251 }
 252 
 253 
 254 /*
 255  * Class:     sun_java2d_cmm_lcms_LCMS
 256  * Method:    loadProfile
 257  * Signature: ([B,Lsun/java2d/cmm/lcms/LCMSProfile;)V
 258  */
 259 JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
 260   (JNIEnv *env, jobject obj, jbyteArray data, jobject disposerRef)
 261 {
 262     jbyte* dataArray;
 263     jint dataSize;
 264     storeID_t sProf;
 265     cmsHPROFILE pf;
 266 
 267     if (JNU_IsNull(env, data)) {
 268         JNU_ThrowIllegalArgumentException(env, "Invalid profile data");
 269         return 0L;
 270     }
 271 
 272     sProf.j = 0L;
 273 
 274     dataArray = (*env)->GetByteArrayElements (env, data, 0);
 275     dataSize = (*env)->GetArrayLength (env, data);
 276 
 277     if (dataArray == NULL) {
 278         JNU_ThrowIllegalArgumentException(env, "Invalid profile data");
 279         return 0L;
 280     }
 281 
 282     pf = cmsOpenProfileFromMem((const void *)dataArray,
 283                                      (cmsUInt32Number) dataSize);
 284 
 285     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
 286 
 287     if (pf == NULL) {
 288         JNU_ThrowIllegalArgumentException(env, "Invalid profile data");
 289     } else {
 290         /* Sanity check: try to save the profile in order
 291          * to force basic validation.
 292          */
 293         cmsUInt32Number pfSize = 0;
 294         if (!cmsSaveProfileToMem(pf, NULL, &pfSize) ||
 295             pfSize < sizeof(cmsICCHeader))
 296         {
 297             JNU_ThrowIllegalArgumentException(env, "Invalid profile data");
 298 
 299             cmsCloseProfile(pf);
 300             pf = NULL;
 301         }
 302     }
 303 
 304     if (pf != NULL) {
 305         // create profile holder
 306         sProf.lcmsPf = (lcmsProfile_p)malloc(sizeof(lcmsProfile_t));
 307         if (sProf.lcmsPf != NULL) {
 308             // register the disposer record
 309             sProf.lcmsPf->pf = pf;
 310             Disposer_AddRecord(env, disposerRef, LCMS_freeProfile, sProf.j);
 311         } else {
 312             cmsCloseProfile(pf);
 313         }
 314     }
 315 
 316     return sProf.j;
 317 }
 318 
 319 /*
 320  * Class:     sun_java2d_cmm_lcms_LCMS
 321  * Method:    getProfileSizeNative
 322  * Signature: (J)I
 323  */
 324 JNIEXPORT jint JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileSizeNative
 325   (JNIEnv *env, jobject obj, jlong id)
 326 {
 327     storeID_t sProf;
 328     cmsUInt32Number pfSize = 0;
 329     sProf.j = id;
 330 
 331     if (cmsSaveProfileToMem(sProf.lcmsPf->pf, NULL, &pfSize) && ((jint)pfSize > 0)) {
 332         return (jint)pfSize;
 333     } else {
 334       JNU_ThrowByName(env, "java/awt/color/CMMException",
 335                       "Can not access specified profile.");
 336         return -1;
 337     }
 338 }
 339 
 340 /*
 341  * Class:     sun_java2d_cmm_lcms_LCMS
 342  * Method:    getProfileDataNative
 343  * Signature: (J[B)V
 344  */
 345 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
 346   (JNIEnv *env, jobject obj, jlong id, jbyteArray data)
 347 {
 348     storeID_t sProf;
 349     jint size;
 350     jbyte* dataArray;
 351     cmsUInt32Number pfSize = 0;
 352     cmsBool status;
 353 
 354     sProf.j = id;
 355 
 356     // determine actual profile size
 357     if (!cmsSaveProfileToMem(sProf.lcmsPf->pf, NULL, &pfSize)) {
 358         JNU_ThrowByName(env, "java/awt/color/CMMException",
 359                         "Can not access specified profile.");
 360         return;
 361     }
 362 
 363     // verify java buffer capacity
 364     size = (*env)->GetArrayLength(env, data);
 365     if (0 >= size || pfSize > (cmsUInt32Number)size) {
 366         JNU_ThrowByName(env, "java/awt/color/CMMException",
 367                         "Insufficient buffer capacity.");
 368         return;
 369     }
 370 
 371     dataArray = (*env)->GetByteArrayElements (env, data, 0);
 372 
 373     status = cmsSaveProfileToMem(sProf.lcmsPf->pf, dataArray, &pfSize);
 374 
 375     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
 376 
 377     if (!status) {
 378         JNU_ThrowByName(env, "java/awt/color/CMMException",
 379                         "Can not access specified profile.");
 380         return;
 381     }
 382 }
 383 
 384 /* Get profile header info */
 385 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize);
 386 static cmsBool _setHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize);
 387 static cmsHPROFILE _writeCookedTag(cmsHPROFILE pfTarget, cmsTagSignature sig, jbyte *pData, jint size);
 388 
 389 
 390 /*
 391  * Class:     sun_java2d_cmm_lcms_LCMS
 392  * Method:    getTagData
 393  * Signature: (JI[B)V
 394  */
 395 JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getTagNative
 396   (JNIEnv *env, jobject obj, jlong id, jint tagSig)
 397 {
 398     storeID_t sProf;
 399     TagSignature_t sig;
 400     cmsInt32Number tagSize;
 401 
 402     jbyte* dataArray = NULL;
 403     jbyteArray data = NULL;
 404 
 405     jint bufSize;
 406 
 407     sProf.j = id;
 408     sig.j = tagSig;
 409 
 410     if (tagSig == SigHead) {
 411         cmsBool status;
 412 
 413         // allocate java array
 414         bufSize = sizeof(cmsICCHeader);
 415         data = (*env)->NewByteArray(env, bufSize);
 416 
 417         if (data == NULL) {
 418             JNU_ThrowByName(env, "java/awt/color/CMMException",
 419                             "Unable to allocate buffer");
 420             return NULL;
 421         }
 422 
 423         dataArray = (*env)->GetByteArrayElements (env, data, 0);
 424 
 425         if (dataArray == NULL) {
 426            JNU_ThrowByName(env, "java/awt/color/CMMException",
 427                             "Unable to get buffer");
 428            return NULL;
 429         }
 430 
 431         status = _getHeaderInfo(sProf.lcmsPf->pf, dataArray, bufSize);
 432 
 433         (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
 434 
 435         if (!status) {
 436             JNU_ThrowByName(env, "java/awt/color/CMMException",
 437                             "ICC Profile header not found");
 438             return NULL;
 439         }
 440 
 441         return data;
 442     }
 443 
 444     if (cmsIsTag(sProf.lcmsPf->pf, sig.cms)) {
 445         tagSize = cmsReadRawTag(sProf.lcmsPf->pf, sig.cms, NULL, 0);
 446     } else {
 447         JNU_ThrowByName(env, "java/awt/color/CMMException",
 448                         "ICC profile tag not found");
 449         return NULL;
 450     }
 451 
 452     // allocate java array
 453     data = (*env)->NewByteArray(env, tagSize);
 454     if (data == NULL) {
 455         JNU_ThrowByName(env, "java/awt/color/CMMException",
 456                         "Unable to allocate buffer");
 457         return NULL;
 458     }
 459 
 460     dataArray = (*env)->GetByteArrayElements (env, data, 0);
 461 
 462     if (dataArray == NULL) {
 463         JNU_ThrowByName(env, "java/awt/color/CMMException",
 464                         "Unable to get buffer");
 465         return NULL;
 466     }
 467 
 468     bufSize = cmsReadRawTag(sProf.lcmsPf->pf, sig.cms, dataArray, tagSize);
 469 
 470     (*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
 471 
 472     if (bufSize != tagSize) {
 473         JNU_ThrowByName(env, "java/awt/color/CMMException",
 474                         "Can not get tag data.");
 475         return NULL;
 476     }
 477     return data;
 478 }
 479 
 480 /*
 481  * Class:     sun_java2d_cmm_lcms_LCMS
 482  * Method:    setTagData
 483  * Signature: (JI[B)V
 484  */
 485 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagDataNative
 486   (JNIEnv *env, jobject obj, jlong id, jint tagSig, jbyteArray data)
 487 {
 488     storeID_t sProf;
 489     cmsHPROFILE pfReplace = NULL;
 490 
 491     TagSignature_t sig;
 492     cmsBool status = FALSE;
 493     jbyte* dataArray;
 494     int tagSize;
 495 
 496     sProf.j = id;
 497     sig.j = tagSig;
 498 
 499     if (JNU_IsNull(env, data)) {
 500         JNU_ThrowIllegalArgumentException(env, "Can not write tag data.");
 501         return;
 502     }
 503 
 504     tagSize =(*env)->GetArrayLength(env, data);
 505 
 506     dataArray = (*env)->GetByteArrayElements(env, data, 0);
 507 
 508     if (dataArray == NULL) {
 509         JNU_ThrowIllegalArgumentException(env, "Can not write tag data.");
 510         return;
 511     }
 512 
 513     if (tagSig == SigHead) {
 514         status  = _setHeaderInfo(sProf.lcmsPf->pf, dataArray, tagSize);
 515     } else {
 516         /*
 517         * New strategy for generic tags: create a place holder,
 518         * dump all existing tags there, dump externally supplied
 519         * tag, and return the new profile to the java.
 520         */
 521         pfReplace = _writeCookedTag(sProf.lcmsPf->pf, sig.cms, dataArray, tagSize);
 522         status = (pfReplace != NULL);
 523     }
 524 
 525     (*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
 526 
 527     if (!status) {
 528         JNU_ThrowIllegalArgumentException(env, "Can not write tag data.");
 529     } else if (pfReplace != NULL) {
 530         cmsCloseProfile(sProf.lcmsPf->pf);
 531         sProf.lcmsPf->pf = pfReplace;
 532     }
 533 }
 534 
 535 void* getILData (JNIEnv *env, jobject img, jint* pDataType,
 536                  jobject* pDataObject) {
 537     void* result = NULL;
 538     *pDataType = (*env)->GetIntField (env, img, IL_dataType_fID);
 539     *pDataObject = (*env)->GetObjectField(env, img, IL_dataArray_fID);
 540     switch (*pDataType) {
 541         case DT_BYTE:
 542             result = (*env)->GetByteArrayElements (env, *pDataObject, 0);
 543             break;
 544         case DT_SHORT:
 545             result = (*env)->GetShortArrayElements (env, *pDataObject, 0);
 546             break;
 547         case DT_INT:
 548             result = (*env)->GetIntArrayElements (env, *pDataObject, 0);
 549             break;
 550         case DT_DOUBLE:
 551             result = (*env)->GetDoubleArrayElements (env, *pDataObject, 0);
 552             break;
 553     }
 554 
 555     return result;
 556 }
 557 
 558 void releaseILData (JNIEnv *env, void* pData, jint dataType,
 559                     jobject dataObject) {
 560     switch (dataType) {
 561         case DT_BYTE:
 562             (*env)->ReleaseByteArrayElements(env,dataObject,(jbyte*)pData,0);
 563             break;
 564         case DT_SHORT:
 565             (*env)->ReleaseShortArrayElements(env,dataObject,(jshort*)pData, 0);
 566             break;
 567         case DT_INT:
 568             (*env)->ReleaseIntArrayElements(env,dataObject,(jint*)pData,0);
 569             break;
 570         case DT_DOUBLE:
 571             (*env)->ReleaseDoubleArrayElements(env,dataObject,(jdouble*)pData,
 572                                                0);
 573             break;
 574     }
 575 }
 576 
 577 /*
 578  * Class:     sun_java2d_cmm_lcms_LCMS
 579  * Method:    colorConvert
 580  * Signature: (Lsun/java2d/cmm/lcms/LCMSTransform;Lsun/java2d/cmm/lcms/LCMSImageLayout;Lsun/java2d/cmm/lcms/LCMSImageLayout;)V
 581  */
 582 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
 583   (JNIEnv *env, jclass obj, jobject trans, jobject src, jobject dst)
 584 {
 585     storeID_t sTrans;
 586     int srcDType, dstDType;
 587     int srcOffset, srcNextRowOffset, dstOffset, dstNextRowOffset;
 588     int width, height, i;
 589     void* inputBuffer;
 590     void* outputBuffer;
 591     char* inputRow;
 592     char* outputRow;
 593     jobject srcData, dstData;
 594     jboolean srcAtOnce = JNI_FALSE, dstAtOnce = JNI_FALSE;
 595 
 596     srcOffset = (*env)->GetIntField (env, src, IL_offset_fID);
 597     srcNextRowOffset = (*env)->GetIntField (env, src, IL_nextRowOffset_fID);
 598     dstOffset = (*env)->GetIntField (env, dst, IL_offset_fID);
 599     dstNextRowOffset = (*env)->GetIntField (env, dst, IL_nextRowOffset_fID);
 600     width = (*env)->GetIntField (env, src, IL_width_fID);
 601     height = (*env)->GetIntField (env, src, IL_height_fID);
 602 
 603     srcAtOnce = (*env)->GetBooleanField(env, src, IL_imageAtOnce_fID);
 604     dstAtOnce = (*env)->GetBooleanField(env, dst, IL_imageAtOnce_fID);
 605 
 606     sTrans.j = (*env)->GetLongField (env, trans, Trans_ID_fID);
 607 
 608     if (sTrans.xf == NULL) {
 609         J2dRlsTraceLn(J2D_TRACE_ERROR, "LCMS_colorConvert: transform == NULL");
 610         JNU_ThrowByName(env, "java/awt/color/CMMException",
 611                         "Cannot get color transform");
 612         return;
 613     }
 614 
 615 
 616     inputBuffer = getILData (env, src, &srcDType, &srcData);
 617 
 618     if (inputBuffer == NULL) {
 619         J2dRlsTraceLn(J2D_TRACE_ERROR, "");
 620         JNU_ThrowByName(env, "java/awt/color/CMMException",
 621                         "Cannot get input data");
 622         return;
 623     }
 624 
 625     outputBuffer = getILData (env, dst, &dstDType, &dstData);
 626 
 627     if (outputBuffer == NULL) {
 628         releaseILData(env, inputBuffer, srcDType, srcData);
 629         JNU_ThrowByName(env, "java/awt/color/CMMException",
 630                         "Cannot get output data");
 631         return;
 632     }
 633 
 634     inputRow = (char*)inputBuffer + srcOffset;
 635     outputRow = (char*)outputBuffer + dstOffset;
 636 
 637     if (srcAtOnce && dstAtOnce) {
 638         cmsDoTransform(sTrans.xf, inputRow, outputRow, width * height);
 639     } else {
 640         for (i = 0; i < height; i++) {
 641             cmsDoTransform(sTrans.xf, inputRow, outputRow, width);
 642             inputRow += srcNextRowOffset;
 643             outputRow += dstNextRowOffset;
 644         }
 645     }
 646 
 647     releaseILData(env, inputBuffer, srcDType, srcData);
 648     releaseILData(env, outputBuffer, dstDType, dstData);
 649 }
 650 
 651 /*
 652  * Class:     sun_java2d_cmm_lcms_LCMS
 653  * Method:    getProfileID
 654  * Signature: (Ljava/awt/color/ICC_Profile;)Lsun/java2d/cmm/lcms/LCMSProfile
 655  */
 656 JNIEXPORT jobject JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileID
 657   (JNIEnv *env, jclass cls, jobject pf)
 658 {
 659     jfieldID fid = (*env)->GetFieldID (env,
 660         (*env)->GetObjectClass(env, pf),
 661         "cmmProfile", "Lsun/java2d/cmm/Profile;");
 662 
 663     jclass clsLcmsProfile = (*env)->FindClass(env,
 664             "sun/java2d/cmm/lcms/LCMSProfile");
 665 
 666     jobject cmmProfile = (*env)->GetObjectField (env, pf, fid);
 667 
 668     if (JNU_IsNull(env, cmmProfile)) {
 669         return NULL;
 670     }
 671     if ((*env)->IsInstanceOf(env, cmmProfile, clsLcmsProfile)) {
 672         return cmmProfile;
 673     }
 674     return NULL;
 675 }
 676 
 677 /*
 678  * Class:     sun_java2d_cmm_lcms_LCMS
 679  * Method:    initLCMS
 680  * Signature: (Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;)V
 681  */
 682 JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_initLCMS
 683   (JNIEnv *env, jclass cls, jclass Trans, jclass IL, jclass Pf)
 684 {
 685     /* TODO: move initialization of the IDs to the static blocks of
 686      * corresponding classes to avoid problems with invalidating ids by class
 687      * unloading
 688      */
 689     Trans_renderType_fID = (*env)->GetFieldID (env, Trans, "renderType", "I");
 690     Trans_ID_fID = (*env)->GetFieldID (env, Trans, "ID", "J");
 691 
 692     IL_isIntPacked_fID = (*env)->GetFieldID (env, IL, "isIntPacked", "Z");
 693     IL_dataType_fID = (*env)->GetFieldID (env, IL, "dataType", "I");
 694     IL_pixelType_fID = (*env)->GetFieldID (env, IL, "pixelType", "I");
 695     IL_dataArray_fID = (*env)->GetFieldID(env, IL, "dataArray",
 696                                           "Ljava/lang/Object;");
 697     IL_width_fID = (*env)->GetFieldID (env, IL, "width", "I");
 698     IL_height_fID = (*env)->GetFieldID (env, IL, "height", "I");
 699     IL_offset_fID = (*env)->GetFieldID (env, IL, "offset", "I");
 700     IL_imageAtOnce_fID = (*env)->GetFieldID (env, IL, "imageAtOnce", "Z");
 701     IL_nextRowOffset_fID = (*env)->GetFieldID (env, IL, "nextRowOffset", "I");
 702 }
 703 
 704 static cmsBool _getHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
 705 {
 706   cmsUInt32Number pfSize = 0;
 707   cmsUInt8Number* pfBuffer = NULL;
 708   cmsBool status = FALSE;
 709 
 710   if (!cmsSaveProfileToMem(pf, NULL, &pfSize) ||
 711       pfSize < sizeof(cmsICCHeader) ||
 712       bufferSize < sizeof(cmsICCHeader))
 713   {
 714     return FALSE;
 715   }
 716 
 717   pfBuffer = malloc(pfSize);
 718   if (pfBuffer == NULL) {
 719     return FALSE;
 720   }
 721 
 722   // load raw profile data into the buffer
 723   if (cmsSaveProfileToMem(pf, pfBuffer, &pfSize)) {
 724     memcpy(pBuffer, pfBuffer, sizeof(cmsICCHeader));
 725     status = TRUE;
 726   }
 727   free(pfBuffer);
 728   return status;
 729 }
 730 
 731 static cmsBool _setHeaderInfo(cmsHPROFILE pf, jbyte* pBuffer, jint bufferSize)
 732 {
 733   cmsICCHeader pfHeader = { 0 };
 734 
 735   if (pBuffer == NULL || bufferSize < sizeof(cmsICCHeader)) {
 736     return FALSE;
 737   }
 738 
 739   memcpy(&pfHeader, pBuffer, sizeof(cmsICCHeader));
 740 
 741   // now set header fields, which we can access using the lcms2 public API
 742   cmsSetHeaderFlags(pf, pfHeader.flags);
 743   cmsSetHeaderManufacturer(pf, pfHeader.manufacturer);
 744   cmsSetHeaderModel(pf, pfHeader.model);
 745   cmsSetHeaderAttributes(pf, pfHeader.attributes);
 746   cmsSetHeaderProfileID(pf, (cmsUInt8Number*)&(pfHeader.profileID));
 747   cmsSetHeaderRenderingIntent(pf, pfHeader.renderingIntent);
 748   cmsSetPCS(pf, pfHeader.pcs);
 749   cmsSetColorSpace(pf, pfHeader.colorSpace);
 750   cmsSetDeviceClass(pf, pfHeader.deviceClass);
 751   cmsSetEncodedICCversion(pf, pfHeader.version);
 752 
 753   return TRUE;
 754 }
 755 
 756 /* Returns new profile handler, if it was created successfully,
 757    NULL otherwise.
 758    */
 759 static cmsHPROFILE _writeCookedTag(const cmsHPROFILE pfTarget,
 760                                const cmsTagSignature sig,
 761                                jbyte *pData, jint size)
 762 {
 763     cmsUInt32Number pfSize = 0;
 764     const cmsInt32Number tagCount = cmsGetTagCount(pfTarget);
 765     cmsInt32Number i;
 766     cmsHPROFILE pfSanity = NULL;
 767 
 768     cmsICCHeader hdr = { 0 };
 769 
 770     cmsHPROFILE p = cmsCreateProfilePlaceholder(NULL);
 771 
 772     if (NULL == p) {
 773         return NULL;
 774     }
 775 
 776     // Populate the placeholder's header according to target profile
 777     hdr.flags = cmsGetHeaderFlags(pfTarget);
 778     hdr.renderingIntent = cmsGetHeaderRenderingIntent(pfTarget);
 779     hdr.manufacturer = cmsGetHeaderManufacturer(pfTarget);
 780     hdr.model = cmsGetHeaderModel(pfTarget);
 781     hdr.pcs = cmsGetPCS(pfTarget);
 782     hdr.colorSpace = cmsGetColorSpace(pfTarget);
 783     hdr.deviceClass = cmsGetDeviceClass(pfTarget);
 784     hdr.version = cmsGetEncodedICCversion(pfTarget);
 785     cmsGetHeaderAttributes(pfTarget, &hdr.attributes);
 786     cmsGetHeaderProfileID(pfTarget, (cmsUInt8Number*)&hdr.profileID);
 787 
 788     cmsSetHeaderFlags(p, hdr.flags);
 789     cmsSetHeaderManufacturer(p, hdr.manufacturer);
 790     cmsSetHeaderModel(p, hdr.model);
 791     cmsSetHeaderAttributes(p, hdr.attributes);
 792     cmsSetHeaderProfileID(p, (cmsUInt8Number*)&(hdr.profileID));
 793     cmsSetHeaderRenderingIntent(p, hdr.renderingIntent);
 794     cmsSetPCS(p, hdr.pcs);
 795     cmsSetColorSpace(p, hdr.colorSpace);
 796     cmsSetDeviceClass(p, hdr.deviceClass);
 797     cmsSetEncodedICCversion(p, hdr.version);
 798 
 799     // now write the user supplied tag
 800     if (size <= 0 || !cmsWriteRawTag(p, sig, pData, size)) {
 801         cmsCloseProfile(p);
 802         return NULL;
 803     }
 804 
 805     // copy tags from the original profile
 806     for (i = 0; i < tagCount; i++) {
 807         cmsBool isTagReady = FALSE;
 808         const cmsTagSignature s = cmsGetTagSignature(pfTarget, i);
 809         const cmsInt32Number tagSize = cmsReadRawTag(pfTarget, s, NULL, 0);
 810 
 811         if (s == sig) {
 812             // skip the user supplied tag
 813             continue;
 814         }
 815 
 816         // read raw tag from the original profile
 817         if (tagSize > 0) {
 818             cmsUInt8Number* buf = (cmsUInt8Number*)malloc(tagSize);
 819             if (buf != NULL) {
 820                 if (tagSize ==  cmsReadRawTag(pfTarget, s, buf, tagSize)) {
 821                     // now we are ready to write the tag
 822                     isTagReady = cmsWriteRawTag(p, s, buf, tagSize);
 823                 }
 824                 free(buf);
 825             }
 826         }
 827 
 828         if (!isTagReady) {
 829             cmsCloseProfile(p);
 830             return NULL;
 831         }
 832     }
 833 
 834     // now we have all tags moved to the new profile.
 835     // do some sanity checks: write it to a memory buffer and read again.
 836     if (cmsSaveProfileToMem(p, NULL, &pfSize)) {
 837         void* buf = malloc(pfSize);
 838         if (buf != NULL) {
 839             // load raw profile data into the buffer
 840             if (cmsSaveProfileToMem(p, buf, &pfSize)) {
 841                 pfSanity = cmsOpenProfileFromMem(buf, pfSize);
 842             }
 843             free(buf);
 844         }
 845     }
 846 
 847     if (pfSanity == NULL) {
 848         // for some reason, we failed to save and read the updated profile
 849         // It likely indicates that the profile is not correct, so we report
 850         // a failure here.
 851         cmsCloseProfile(p);
 852         p =  NULL;
 853     } else {
 854         // do final check whether we can read and handle the the target tag.
 855         const void* pTag = cmsReadTag(pfSanity, sig);
 856         if (pTag == NULL) {
 857             // the tag can not be cooked
 858             cmsCloseProfile(p);
 859             p = NULL;
 860         }
 861         cmsCloseProfile(pfSanity);
 862         pfSanity = NULL;
 863     }
 864 
 865     return p;
 866 }