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