1 /* 2 * Copyright (c) 2007, 2010, 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 /********************************************************************** 27 ********************************************************************** 28 ********************************************************************** 29 *** COPYRIGHT (c) Eastman Kodak Company, 1997 *** 30 *** As an unpublished work pursuant to Title 17 of the United *** 31 *** States Code. All rights reserved. *** 32 ********************************************************************** 33 ********************************************************************** 34 **********************************************************************/ 35 36 package sun.java2d.cmm.lcms; 37 38 import java.awt.color.ICC_Profile; 39 import java.awt.color.ProfileDataException; 40 import java.awt.color.CMMException; 41 import java.awt.color.ColorSpace; 42 import java.awt.image.BufferedImage; 43 import java.awt.image.Raster; 44 import java.awt.image.WritableRaster; 45 import java.awt.image.ColorModel; 46 import java.awt.image.DirectColorModel; 47 import java.awt.image.ComponentColorModel; 48 import java.awt.image.SampleModel; 49 import java.awt.image.DataBuffer; 50 import java.awt.image.SinglePixelPackedSampleModel; 51 import java.awt.image.ComponentSampleModel; 52 import sun.java2d.cmm.*; 53 import sun.java2d.cmm.lcms.*; 54 55 56 public class LCMSTransform implements ColorTransform { 57 long ID; 58 private int inFormatter = 0; 59 private boolean isInIntPacked = false; 60 private int outFormatter = 0; 61 private boolean isOutIntPacked = false; 62 63 ICC_Profile[] profiles; 64 long [] profileIDs; 65 int renderType; 66 int transformType; 67 68 private int numInComponents = -1; 69 private int numOutComponents = -1; 70 71 private Object disposerReferent = new Object(); 72 73 /* the class initializer */ 74 static { 75 if (ProfileDeferralMgr.deferring) { 76 ProfileDeferralMgr.activateProfiles(); 77 } 78 } 79 80 public LCMSTransform(ICC_Profile profile, int renderType, 81 int transformType) 82 { 83 /* Actually, it is not a complete transform but just part of it */ 84 profiles = new ICC_Profile[1]; 85 profiles[0] = profile; 86 profileIDs = new long[1]; 87 profileIDs[0] = LCMS.getProfileID(profile); 88 this.renderType = (renderType == ColorTransform.Any)? 89 ICC_Profile.icPerceptual : renderType; 90 this.transformType = transformType; 91 92 /* Note that ICC_Profile.getNumComponents() is quite expensive 93 * (it may results in a reading of the profile header). 94 * So, here we cache the number of components of input and 95 * output profiles for further usage. 96 */ 97 numInComponents = profiles[0].getNumComponents(); 98 numOutComponents = profiles[profiles.length - 1].getNumComponents(); 99 } 100 101 public LCMSTransform (ColorTransform[] transforms) { 102 int size = 0; 103 for (int i=0; i < transforms.length; i++) { 104 size+=((LCMSTransform)transforms[i]).profiles.length; 105 } 106 profiles = new ICC_Profile[size]; 107 profileIDs = new long[size]; 108 int j = 0; 109 for (int i=0; i < transforms.length; i++) { 110 LCMSTransform curTrans = (LCMSTransform)transforms[i]; 111 System.arraycopy(curTrans.profiles, 0, profiles, j, 112 curTrans.profiles.length); 113 System.arraycopy(curTrans.profileIDs, 0, profileIDs, j, 114 curTrans.profileIDs.length); 115 j += curTrans.profiles.length; 116 } 117 renderType = ((LCMSTransform)transforms[0]).renderType; 118 119 /* Note that ICC_Profile.getNumComponents() is quite expensive 120 * (it may results in a reading of the profile header). 121 * So, here we cache the number of components of input and 122 * output profiles for further usage. 123 */ 124 numInComponents = profiles[0].getNumComponents(); 125 numOutComponents = profiles[profiles.length - 1].getNumComponents(); 126 } 127 128 public int getNumInComponents() { 129 return numInComponents; 130 } 131 132 public int getNumOutComponents() { 133 return numOutComponents; 134 } 135 136 private synchronized void doTransform(LCMSImageLayout in, 137 LCMSImageLayout out) { 138 // update native transfrom if needed 139 if (ID == 0L || 140 inFormatter != in.pixelType || isInIntPacked != in.isIntPacked || 141 outFormatter != out.pixelType || isOutIntPacked != out.isIntPacked) 142 { 143 144 if (ID != 0L) { 145 // Disposer will destroy forgotten transform 146 disposerReferent = new Object(); 147 } 148 inFormatter = in.pixelType; 149 isInIntPacked = in.isIntPacked; 150 151 outFormatter = out.pixelType; 152 isOutIntPacked = out.isIntPacked; 153 154 ID = LCMS.createNativeTransform(profileIDs, renderType, 155 inFormatter, isInIntPacked, 156 outFormatter, isOutIntPacked, 157 disposerReferent); 158 } 159 160 LCMS.colorConvert(this, in, out); 161 } 162 163 public void colorConvert(BufferedImage src, BufferedImage dst) { 164 if (LCMSImageLayout.isSupported(src) && 165 LCMSImageLayout.isSupported(dst)) 166 { 167 doTransform(new LCMSImageLayout(src), new LCMSImageLayout(dst)); 168 return; 169 } 170 LCMSImageLayout srcIL, dstIL; 171 Raster srcRas = src.getRaster(); 172 WritableRaster dstRas = dst.getRaster(); 173 ColorModel srcCM = src.getColorModel(); 174 ColorModel dstCM = dst.getColorModel(); 175 int w = src.getWidth(); 176 int h = src.getHeight(); 177 int srcNumComp = srcCM.getNumColorComponents(); 178 int dstNumComp = dstCM.getNumColorComponents(); 179 int precision = 8; 180 float maxNum = 255.0f; 181 for (int i = 0; i < srcNumComp; i++) { 182 if (srcCM.getComponentSize(i) > 8) { 183 precision = 16; 184 maxNum = 65535.0f; 185 } 186 } 187 for (int i = 0; i < dstNumComp; i++) { 188 if (dstCM.getComponentSize(i) > 8) { 189 precision = 16; 190 maxNum = 65535.0f; 191 } 192 } 193 float[] srcMinVal = new float[srcNumComp]; 194 float[] srcInvDiffMinMax = new float[srcNumComp]; 195 ColorSpace cs = srcCM.getColorSpace(); 196 for (int i = 0; i < srcNumComp; i++) { 197 srcMinVal[i] = cs.getMinValue(i); 198 srcInvDiffMinMax[i] = maxNum / (cs.getMaxValue(i) - srcMinVal[i]); 199 } 200 cs = dstCM.getColorSpace(); 201 float[] dstMinVal = new float[dstNumComp]; 202 float[] dstDiffMinMax = new float[dstNumComp]; 203 for (int i = 0; i < dstNumComp; i++) { 204 dstMinVal[i] = cs.getMinValue(i); 205 dstDiffMinMax[i] = (cs.getMaxValue(i) - dstMinVal[i]) / maxNum; 206 } 207 boolean dstHasAlpha = dstCM.hasAlpha(); 208 boolean needSrcAlpha = srcCM.hasAlpha() && dstHasAlpha; 209 float[] dstColor; 210 if (dstHasAlpha) { 211 dstColor = new float[dstNumComp + 1]; 212 } else { 213 dstColor = new float[dstNumComp]; 214 } 215 if (precision == 8) { 216 byte[] srcLine = new byte[w * srcNumComp]; 217 byte[] dstLine = new byte[w * dstNumComp]; 218 Object pixel; 219 float[] color; 220 float[] alpha = null; 221 if (needSrcAlpha) { 222 alpha = new float[w]; 223 } 224 int idx; 225 // TODO check for src npixels = dst npixels 226 srcIL = new LCMSImageLayout( 227 srcLine, srcLine.length/getNumInComponents(), 228 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 229 LCMSImageLayout.BYTES_SH(1), getNumInComponents()); 230 dstIL = new LCMSImageLayout( 231 dstLine, dstLine.length/getNumOutComponents(), 232 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 233 LCMSImageLayout.BYTES_SH(1), getNumOutComponents()); 234 // process each scanline 235 for (int y = 0; y < h; y++) { 236 // convert src scanline 237 pixel = null; 238 color = null; 239 idx = 0; 240 for (int x = 0; x < w; x++) { 241 pixel = srcRas.getDataElements(x, y, pixel); 242 color = srcCM.getNormalizedComponents(pixel, color, 0); 243 for (int i = 0; i < srcNumComp; i++) { 244 srcLine[idx++] = (byte) 245 ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] + 246 0.5f); 247 } 248 if (needSrcAlpha) { 249 alpha[x] = color[srcNumComp]; 250 } 251 } 252 // color convert srcLine to dstLine 253 doTransform(srcIL, dstIL); 254 255 // convert dst scanline 256 pixel = null; 257 idx = 0; 258 for (int x = 0; x < w; x++) { 259 for (int i = 0; i < dstNumComp; i++) { 260 dstColor[i] = ((float) (dstLine[idx++] & 0xff)) * 261 dstDiffMinMax[i] + dstMinVal[i]; 262 } 263 if (needSrcAlpha) { 264 dstColor[dstNumComp] = alpha[x]; 265 } else if (dstHasAlpha) { 266 dstColor[dstNumComp] = 1.0f; 267 } 268 pixel = dstCM.getDataElements(dstColor, 0, pixel); 269 dstRas.setDataElements(x, y, pixel); 270 } 271 } 272 } else { 273 short[] srcLine = new short[w * srcNumComp]; 274 short[] dstLine = new short[w * dstNumComp]; 275 Object pixel; 276 float[] color; 277 float[] alpha = null; 278 if (needSrcAlpha) { 279 alpha = new float[w]; 280 } 281 int idx; 282 srcIL = new LCMSImageLayout( 283 srcLine, srcLine.length/getNumInComponents(), 284 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 285 LCMSImageLayout.BYTES_SH(2), getNumInComponents()*2); 286 287 dstIL = new LCMSImageLayout( 288 dstLine, dstLine.length/getNumOutComponents(), 289 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 290 LCMSImageLayout.BYTES_SH(2), getNumOutComponents()*2); 291 292 // process each scanline 293 for (int y = 0; y < h; y++) { 294 // convert src scanline 295 pixel = null; 296 color = null; 297 idx = 0; 298 for (int x = 0; x < w; x++) { 299 pixel = srcRas.getDataElements(x, y, pixel); 300 color = srcCM.getNormalizedComponents(pixel, color, 0); 301 for (int i = 0; i < srcNumComp; i++) { 302 srcLine[idx++] = (short) 303 ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] + 304 0.5f); 305 } 306 if (needSrcAlpha) { 307 alpha[x] = color[srcNumComp]; 308 } 309 } 310 // color convert srcLine to dstLine 311 doTransform(srcIL, dstIL); 312 313 // convert dst scanline 314 pixel = null; 315 idx = 0; 316 for (int x = 0; x < w; x++) { 317 for (int i = 0; i < dstNumComp; i++) { 318 dstColor[i] = ((float) (dstLine[idx++] & 0xffff)) * 319 dstDiffMinMax[i] + dstMinVal[i]; 320 } 321 if (needSrcAlpha) { 322 dstColor[dstNumComp] = alpha[x]; 323 } else if (dstHasAlpha) { 324 dstColor[dstNumComp] = 1.0f; 325 } 326 pixel = dstCM.getDataElements(dstColor, 0, pixel); 327 dstRas.setDataElements(x, y, pixel); 328 } 329 } 330 } 331 } 332 333 public void colorConvert(Raster src, WritableRaster dst, 334 float[] srcMinVal, float[]srcMaxVal, 335 float[] dstMinVal, float[]dstMaxVal) { 336 LCMSImageLayout srcIL, dstIL; 337 338 // Can't pass src and dst directly to CMM, so process per scanline 339 SampleModel srcSM = src.getSampleModel(); 340 SampleModel dstSM = dst.getSampleModel(); 341 int srcTransferType = src.getTransferType(); 342 int dstTransferType = dst.getTransferType(); 343 boolean srcIsFloat, dstIsFloat; 344 if ((srcTransferType == DataBuffer.TYPE_FLOAT) || 345 (srcTransferType == DataBuffer.TYPE_DOUBLE)) { 346 srcIsFloat = true; 347 } else { 348 srcIsFloat = false; 349 } 350 if ((dstTransferType == DataBuffer.TYPE_FLOAT) || 351 (dstTransferType == DataBuffer.TYPE_DOUBLE)) { 352 dstIsFloat = true; 353 } else { 354 dstIsFloat = false; 355 } 356 int w = src.getWidth(); 357 int h = src.getHeight(); 358 int srcNumBands = src.getNumBands(); 359 int dstNumBands = dst.getNumBands(); 360 float[] srcScaleFactor = new float[srcNumBands]; 361 float[] dstScaleFactor = new float[dstNumBands]; 362 float[] srcUseMinVal = new float[srcNumBands]; 363 float[] dstUseMinVal = new float[dstNumBands]; 364 for (int i = 0; i < srcNumBands; i++) { 365 if (srcIsFloat) { 366 srcScaleFactor[i] = 65535.0f / (srcMaxVal[i] - srcMinVal[i]); 367 srcUseMinVal[i] = srcMinVal[i]; 368 } else { 369 if (srcTransferType == DataBuffer.TYPE_SHORT) { 370 srcScaleFactor[i] = 65535.0f / 32767.0f; 371 } else { 372 srcScaleFactor[i] = 65535.0f / 373 ((float) ((1 << srcSM.getSampleSize(i)) - 1)); 374 } 375 srcUseMinVal[i] = 0.0f; 376 } 377 } 378 for (int i = 0; i < dstNumBands; i++) { 379 if (dstIsFloat) { 380 dstScaleFactor[i] = (dstMaxVal[i] - dstMinVal[i]) / 65535.0f; 381 dstUseMinVal[i] = dstMinVal[i]; 382 } else { 383 if (dstTransferType == DataBuffer.TYPE_SHORT) { 384 dstScaleFactor[i] = 32767.0f / 65535.0f; 385 } else { 386 dstScaleFactor[i] = 387 ((float) ((1 << dstSM.getSampleSize(i)) - 1)) / 388 65535.0f; 389 } 390 dstUseMinVal[i] = 0.0f; 391 } 392 } 393 int ys = src.getMinY(); 394 int yd = dst.getMinY(); 395 int xs, xd; 396 float sample; 397 short[] srcLine = new short[w * srcNumBands]; 398 short[] dstLine = new short[w * dstNumBands]; 399 int idx; 400 srcIL = new LCMSImageLayout( 401 srcLine, srcLine.length/getNumInComponents(), 402 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 403 LCMSImageLayout.BYTES_SH(2), getNumInComponents()*2); 404 405 dstIL = new LCMSImageLayout( 406 dstLine, dstLine.length/getNumOutComponents(), 407 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 408 LCMSImageLayout.BYTES_SH(2), getNumOutComponents()*2); 409 410 // process each scanline 411 for (int y = 0; y < h; y++, ys++, yd++) { 412 // get src scanline 413 xs = src.getMinX(); 414 idx = 0; 415 for (int x = 0; x < w; x++, xs++) { 416 for (int i = 0; i < srcNumBands; i++) { 417 sample = src.getSampleFloat(xs, ys, i); 418 srcLine[idx++] = (short) 419 ((sample - srcUseMinVal[i]) * srcScaleFactor[i] + 0.5f); 420 } 421 } 422 423 // color convert srcLine to dstLine 424 doTransform(srcIL, dstIL); 425 426 // store dst scanline 427 xd = dst.getMinX(); 428 idx = 0; 429 for (int x = 0; x < w; x++, xd++) { 430 for (int i = 0; i < dstNumBands; i++) { 431 sample = ((dstLine[idx++] & 0xffff) * dstScaleFactor[i]) + 432 dstUseMinVal[i]; 433 dst.setSample(xd, yd, i, sample); 434 } 435 } 436 } 437 } 438 439 public void colorConvert(Raster src, WritableRaster dst) { 440 441 LCMSImageLayout srcIL, dstIL; 442 // Can't pass src and dst directly to CMM, so process per scanline 443 SampleModel srcSM = src.getSampleModel(); 444 SampleModel dstSM = dst.getSampleModel(); 445 int srcTransferType = src.getTransferType(); 446 int dstTransferType = dst.getTransferType(); 447 int w = src.getWidth(); 448 int h = src.getHeight(); 449 int srcNumBands = src.getNumBands(); 450 int dstNumBands = dst.getNumBands(); 451 int precision = 8; 452 float maxNum = 255.0f; 453 for (int i = 0; i < srcNumBands; i++) { 454 if (srcSM.getSampleSize(i) > 8) { 455 precision = 16; 456 maxNum = 65535.0f; 457 } 458 } 459 for (int i = 0; i < dstNumBands; i++) { 460 if (dstSM.getSampleSize(i) > 8) { 461 precision = 16; 462 maxNum = 65535.0f; 463 } 464 } 465 float[] srcScaleFactor = new float[srcNumBands]; 466 float[] dstScaleFactor = new float[dstNumBands]; 467 for (int i = 0; i < srcNumBands; i++) { 468 if (srcTransferType == DataBuffer.TYPE_SHORT) { 469 srcScaleFactor[i] = maxNum / 32767.0f; 470 } else { 471 srcScaleFactor[i] = maxNum / 472 ((float) ((1 << srcSM.getSampleSize(i)) - 1)); 473 } 474 } 475 for (int i = 0; i < dstNumBands; i++) { 476 if (dstTransferType == DataBuffer.TYPE_SHORT) { 477 dstScaleFactor[i] = 32767.0f / maxNum; 478 } else { 479 dstScaleFactor[i] = 480 ((float) ((1 << dstSM.getSampleSize(i)) - 1)) / maxNum; 481 } 482 } 483 int ys = src.getMinY(); 484 int yd = dst.getMinY(); 485 int xs, xd; 486 int sample; 487 if (precision == 8) { 488 byte[] srcLine = new byte[w * srcNumBands]; 489 byte[] dstLine = new byte[w * dstNumBands]; 490 int idx; 491 // TODO check for src npixels = dst npixels 492 srcIL = new LCMSImageLayout( 493 srcLine, srcLine.length/getNumInComponents(), 494 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 495 LCMSImageLayout.BYTES_SH(1), getNumInComponents()); 496 dstIL = new LCMSImageLayout( 497 dstLine, dstLine.length/getNumOutComponents(), 498 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 499 LCMSImageLayout.BYTES_SH(1), getNumOutComponents()); 500 501 // process each scanline 502 for (int y = 0; y < h; y++, ys++, yd++) { 503 // get src scanline 504 xs = src.getMinX(); 505 idx = 0; 506 for (int x = 0; x < w; x++, xs++) { 507 for (int i = 0; i < srcNumBands; i++) { 508 sample = src.getSample(xs, ys, i); 509 srcLine[idx++] = (byte) 510 ((sample * srcScaleFactor[i]) + 0.5f); 511 } 512 } 513 514 // color convert srcLine to dstLine 515 doTransform(srcIL, dstIL); 516 517 // store dst scanline 518 xd = dst.getMinX(); 519 idx = 0; 520 for (int x = 0; x < w; x++, xd++) { 521 for (int i = 0; i < dstNumBands; i++) { 522 sample = (int) (((dstLine[idx++] & 0xff) * 523 dstScaleFactor[i]) + 0.5f); 524 dst.setSample(xd, yd, i, sample); 525 } 526 } 527 } 528 } else { 529 short[] srcLine = new short[w * srcNumBands]; 530 short[] dstLine = new short[w * dstNumBands]; 531 int idx; 532 srcIL = new LCMSImageLayout( 533 srcLine, srcLine.length/getNumInComponents(), 534 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 535 LCMSImageLayout.BYTES_SH(2), getNumInComponents()*2); 536 537 dstIL = new LCMSImageLayout( 538 dstLine, dstLine.length/getNumOutComponents(), 539 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 540 LCMSImageLayout.BYTES_SH(2), getNumOutComponents()*2); 541 542 // process each scanline 543 for (int y = 0; y < h; y++, ys++, yd++) { 544 // get src scanline 545 xs = src.getMinX(); 546 idx = 0; 547 for (int x = 0; x < w; x++, xs++) { 548 for (int i = 0; i < srcNumBands; i++) { 549 sample = src.getSample(xs, ys, i); 550 srcLine[idx++] = (short) 551 ((sample * srcScaleFactor[i]) + 0.5f); 552 } 553 } 554 555 // color convert srcLine to dstLine 556 doTransform(srcIL, dstIL); 557 558 // store dst scanline 559 xd = dst.getMinX(); 560 idx = 0; 561 for (int x = 0; x < w; x++, xd++) { 562 for (int i = 0; i < dstNumBands; i++) { 563 sample = (int) (((dstLine[idx++] & 0xffff) * 564 dstScaleFactor[i]) + 0.5f); 565 dst.setSample(xd, yd, i, sample); 566 } 567 } 568 } 569 } 570 } 571 572 /* convert an array of colors in short format */ 573 /* each color is a contiguous set of array elements */ 574 /* the number of colors is (size of the array) / (number of input/output 575 components */ 576 public short[] colorConvert(short[] src, short[] dst) { 577 578 if (dst == null) { 579 dst = new short [(src.length/getNumInComponents())*getNumOutComponents()]; 580 } 581 582 LCMSImageLayout srcIL = new LCMSImageLayout( 583 src, src.length/getNumInComponents(), 584 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 585 LCMSImageLayout.BYTES_SH(2), getNumInComponents()*2); 586 587 LCMSImageLayout dstIL = new LCMSImageLayout( 588 dst, dst.length/getNumOutComponents(), 589 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 590 LCMSImageLayout.BYTES_SH(2), getNumOutComponents()*2); 591 592 doTransform(srcIL, dstIL); 593 594 return dst; 595 } 596 597 public byte[] colorConvert(byte[] src, byte[] dst) { 598 if (dst == null) { 599 dst = new byte [(src.length/getNumInComponents())*getNumOutComponents()]; 600 } 601 602 LCMSImageLayout srcIL = new LCMSImageLayout( 603 src, src.length/getNumInComponents(), 604 LCMSImageLayout.CHANNELS_SH(getNumInComponents()) | 605 LCMSImageLayout.BYTES_SH(1), getNumInComponents()); 606 607 LCMSImageLayout dstIL = new LCMSImageLayout( 608 dst, dst.length/getNumOutComponents(), 609 LCMSImageLayout.CHANNELS_SH(getNumOutComponents()) | 610 LCMSImageLayout.BYTES_SH(1), getNumOutComponents()); 611 612 doTransform(srcIL, dstIL); 613 614 return dst; 615 } 616 }