1. /*
  2. * @(#)ColorConvertOp.java 1.37 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /**********************************************************************
  8. **********************************************************************
  9. **********************************************************************
  10. *** COPYRIGHT (c) Eastman Kodak Company, 1997 ***
  11. *** As an unpublished work pursuant to Title 17 of the United ***
  12. *** States Code. All rights reserved. ***
  13. **********************************************************************
  14. **********************************************************************
  15. **********************************************************************/
  16. package java.awt.image;
  17. import java.awt.Point;
  18. import java.awt.Graphics2D;
  19. import java.awt.color.*;
  20. import sun.awt.color.ICC_Transform;
  21. import sun.awt.color.ProfileDeferralMgr;
  22. import java.awt.geom.Rectangle2D;
  23. import java.awt.geom.Point2D;
  24. import java.awt.RenderingHints;
  25. /**
  26. * This class performs a pixel-by-pixel color conversion of the data in
  27. * the source image. The resulting color values are scaled to the precision
  28. * of the destination image. Color conversion can be specified
  29. * via an array of ColorSpace objects or an array of ICC_Profile objects.
  30. * <p>
  31. * If the source is a BufferedImage with premultiplied alpha, the
  32. * color components are divided by the alpha component before color conversion.
  33. * If the destination is a BufferedImage with premultiplied alpha, the
  34. * color components are multiplied by the alpha component after conversion.
  35. * Rasters are treated as having no alpha channel, i.e. all bands are
  36. * color bands.
  37. * <p>
  38. * If a RenderingHints object is specified in the constructor, the
  39. * color rendering hint and the dithering hint may be used to control
  40. * color conversion.
  41. * <p>
  42. * Note that Source and Destination may be the same object.
  43. * <p>
  44. * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
  45. * @see java.awt.RenderingHints#KEY_DITHERING
  46. */
  47. public class ColorConvertOp implements BufferedImageOp, RasterOp {
  48. ICC_Profile[] profileList;
  49. ColorSpace[] CSList;
  50. ICC_Transform thisTransform, thisRasterTransform;
  51. ICC_Profile thisSrcProfile, thisDestProfile;
  52. RenderingHints hints;
  53. boolean gotProfiles;
  54. float[] srcMinVals, srcMaxVals, dstMinVals, dstMaxVals;
  55. /* the class initializer */
  56. static {
  57. if (ProfileDeferralMgr.deferring) {
  58. ProfileDeferralMgr.activateProfiles();
  59. }
  60. }
  61. /**
  62. * Constructs a new ColorConvertOp which will convert
  63. * from a source color space to a destination color space.
  64. * The RenderingHints argument may be null.
  65. * This Op can be used only with BufferedImages, and will convert
  66. * directly from the ColorSpace of the source image to that of the
  67. * destination. The destination argument of the filter method
  68. * cannot be specified as null.
  69. * @param hints the <code>RenderingHints</code> object used to control
  70. * the color conversion, or <code>null</code>
  71. */
  72. public ColorConvertOp (RenderingHints hints)
  73. {
  74. profileList = new ICC_Profile [0]; /* 0 length list */
  75. this.hints = hints;
  76. }
  77. /**
  78. * Constructs a new ColorConvertOp from a ColorSpace object.
  79. * The RenderingHints argument may be null. This
  80. * Op can be used only with BufferedImages, and is primarily useful
  81. * when the {@link #filter(BufferedImage, BufferedImage) filter}
  82. * method is invoked with a destination argument of null.
  83. * In that case, the ColorSpace defines the destination color space
  84. * for the destination created by the filter method. Otherwise, the
  85. * ColorSpace defines an intermediate space to which the source is
  86. * converted before being converted to the destination space.
  87. * @param cspace defines the destination <code>ColorSpace</code> or an
  88. * intermediate <code>ColorSpace</code>
  89. * @param hints the <code>RenderingHints</code> object used to control
  90. * the color conversion, or <code>null</code>
  91. * @throws NullPointerException if cspace is null
  92. */
  93. public ColorConvertOp (ColorSpace cspace, RenderingHints hints)
  94. {
  95. if (cspace == null) {
  96. throw new NullPointerException("ColorSpace cannot be null");
  97. }
  98. if (cspace instanceof ICC_ColorSpace) {
  99. profileList = new ICC_Profile [1]; /* 1 profile in the list */
  100. profileList [0] = ((ICC_ColorSpace) cspace).getProfile();
  101. }
  102. else {
  103. CSList = new ColorSpace[1]; /* non-ICC case: 1 ColorSpace in list */
  104. CSList[0] = cspace;
  105. }
  106. this.hints = hints;
  107. }
  108. /**
  109. * Constructs a new ColorConvertOp from two ColorSpace objects.
  110. * The RenderingHints argument may be null.
  111. * This Op is primarily useful for calling the filter method on
  112. * Rasters, in which case the two ColorSpaces define the operation
  113. * to be performed on the Rasters. In that case, the number of bands
  114. * in the source Raster must match the number of components in
  115. * srcCspace, and the number of bands in the destination Raster
  116. * must match the number of components in dstCspace. For BufferedImages,
  117. * the two ColorSpaces define intermediate spaces through which the
  118. * source is converted before being converted to the destination space.
  119. * @param srcCspace the source <code>ColorSpace</code>
  120. * @param dstCspace the destination <code>ColorSpace</code>
  121. * @param hints the <code>RenderingHints</code> object used to control
  122. * the color conversion, or <code>null</code>
  123. * @throws NullPointerException if either srcCspace or dstCspace is null
  124. */
  125. public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace,
  126. RenderingHints hints)
  127. {
  128. if ((srcCspace == null) || (dstCspace == null)) {
  129. throw new NullPointerException("ColorSpaces cannot be null");
  130. }
  131. if ((srcCspace instanceof ICC_ColorSpace) &&
  132. (dstCspace instanceof ICC_ColorSpace)) {
  133. profileList = new ICC_Profile [2]; /* 2 profiles in the list */
  134. profileList [0] = ((ICC_ColorSpace) srcCspace).getProfile();
  135. profileList [1] = ((ICC_ColorSpace) dstCspace).getProfile();
  136. getMinMaxValsFromColorSpaces(srcCspace, dstCspace);
  137. } else {
  138. /* non-ICC case: 2 ColorSpaces in list */
  139. CSList = new ColorSpace[2];
  140. CSList[0] = srcCspace;
  141. CSList[1] = dstCspace;
  142. }
  143. this.hints = hints;
  144. }
  145. /**
  146. * Constructs a new ColorConvertOp from an array of ICC_Profiles.
  147. * The RenderingHints argument may be null.
  148. * The sequence of profiles may include profiles that represent color
  149. * spaces, profiles that represent effects, etc. If the whole sequence
  150. * does not represent a well-defined color conversion, an exception is
  151. * thrown.
  152. * <p>For BufferedImages, if the ColorSpace
  153. * of the source BufferedImage does not match the requirements of the
  154. * first profile in the array,
  155. * the first conversion is to an appropriate ColorSpace.
  156. * If the requirements of the last profile in the array are not met
  157. * by the ColorSpace of the destination BufferedImage,
  158. * the last conversion is to the destination's ColorSpace.
  159. * <p>For Rasters, the number of bands in the source Raster must match
  160. * the requirements of the first profile in the array, and the
  161. * number of bands in the destination Raster must match the requirements
  162. * of the last profile in the array. The array must have at least two
  163. * elements or calling the filter method for Rasters will throw an
  164. * IllegalArgumentException.
  165. * @param profiles the array of <code>ICC_Profile</code> objects
  166. * @param hints the <code>RenderingHints</code> object used to control
  167. * the color conversion, or <code>null</code>
  168. * @exception IllegalArgumentException when the profile sequence does not
  169. * specify a well-defined color conversion
  170. * @exception NullPointerException if profiles is null
  171. */
  172. public ColorConvertOp (ICC_Profile[] profiles, RenderingHints hints)
  173. {
  174. if (profiles == null) {
  175. throw new NullPointerException("Profiles cannot be null");
  176. }
  177. gotProfiles = true;
  178. profileList = new ICC_Profile[profiles.length];
  179. for (int i1 = 0; i1 < profiles.length; i1++) {
  180. profileList[i1] = profiles[i1];
  181. }
  182. this.hints = hints;
  183. }
  184. /**
  185. * Returns the array of ICC_Profiles used to construct this ColorConvertOp.
  186. * Returns null if the ColorConvertOp was not constructed from such an
  187. * array.
  188. * @return the array of <code>ICC_Profile</code> objects of this
  189. * <code>ColorConvertOp</code>, or <code>null</code> if this
  190. * <code>ColorConvertOp</code> was not constructed with an
  191. * array of <code>ICC_Profile</code> objects.
  192. */
  193. public final ICC_Profile[] getICC_Profiles() {
  194. if (gotProfiles) {
  195. ICC_Profile[] profiles = new ICC_Profile[profileList.length];
  196. for (int i1 = 0; i1 < profileList.length; i1++) {
  197. profiles[i1] = profileList[i1];
  198. }
  199. return profiles;
  200. }
  201. return null;
  202. }
  203. /**
  204. * ColorConverts the source BufferedImage.
  205. * If the destination image is null,
  206. * a BufferedImage will be created with an appropriate ColorModel.
  207. * @param src the source <code>BufferedImage</code> to be converted
  208. * @param dest the destination <code>BufferedImage</code>,
  209. * or <code>null</code>
  210. * @return <code>dest</code> color converted from <code>src</code>
  211. * or a new, converted <code>BufferedImage</code>
  212. * if <code>dest</code> is <code>null</code>
  213. * @exception IllegalArgumentException if dest is null and this op was
  214. * constructed using the constructor which takes only a
  215. * RenderingHints argument, since the operation is ill defined.
  216. */
  217. public final BufferedImage filter(BufferedImage src, BufferedImage dest) {
  218. ColorSpace srcColorSpace, destColorSpace;
  219. BufferedImage savdest = null;
  220. if (src.getColorModel() instanceof IndexColorModel) {
  221. IndexColorModel icm = (IndexColorModel) src.getColorModel();
  222. src = icm.convertToIntDiscrete(src.getRaster(), true);
  223. }
  224. srcColorSpace = src.getColorModel().getColorSpace();
  225. if (dest != null) {
  226. if (dest.getColorModel() instanceof IndexColorModel) {
  227. savdest = dest;
  228. dest = null;
  229. destColorSpace = null;
  230. } else {
  231. destColorSpace = dest.getColorModel().getColorSpace();
  232. }
  233. } else {
  234. destColorSpace = null;
  235. }
  236. if ((CSList != null) ||
  237. (!(srcColorSpace instanceof ICC_ColorSpace)) ||
  238. ((dest != null) &&
  239. (!(destColorSpace instanceof ICC_ColorSpace)))) {
  240. /* non-ICC case */
  241. dest = nonICCBIFilter(src, srcColorSpace, dest, destColorSpace);
  242. } else {
  243. dest = ICCBIFilter(src, srcColorSpace, dest, destColorSpace);
  244. }
  245. if (savdest != null) {
  246. Graphics2D big = savdest.createGraphics();
  247. try {
  248. big.drawImage(dest, 0, 0, null);
  249. } finally {
  250. big.dispose();
  251. }
  252. return savdest;
  253. } else {
  254. return dest;
  255. }
  256. }
  257. private final BufferedImage ICCBIFilter(BufferedImage src,
  258. ColorSpace srcColorSpace,
  259. BufferedImage dest,
  260. ColorSpace destColorSpace) {
  261. int nProfiles = profileList.length;
  262. ICC_Profile srcProfile = null, destProfile = null;
  263. srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
  264. if (dest == null) { /* last profile in the list defines
  265. the output color space */
  266. if (nProfiles == 0) {
  267. throw new IllegalArgumentException(
  268. "Destination ColorSpace is undefined");
  269. }
  270. destProfile = profileList [nProfiles - 1];
  271. dest = createCompatibleDestImage(src, null);
  272. }
  273. else {
  274. if (src.getHeight() != dest.getHeight() ||
  275. src.getWidth() != dest.getWidth()) {
  276. throw new IllegalArgumentException(
  277. "Width or height of BufferedImages do not match");
  278. }
  279. destProfile = ((ICC_ColorSpace) destColorSpace).getProfile();
  280. }
  281. /* make a new transform if needed */
  282. if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
  283. (thisDestProfile != destProfile) ) {
  284. updateBITransform(srcProfile, destProfile);
  285. }
  286. /* color convert the image */
  287. thisTransform.colorConvert(src, dest);
  288. return dest;
  289. }
  290. private void updateBITransform(ICC_Profile srcProfile,
  291. ICC_Profile destProfile) {
  292. ICC_Profile[] theProfiles;
  293. int i1, nProfiles, nTransforms, whichTrans, renderState;
  294. ICC_Transform[] theTransforms;
  295. boolean useSrc = false, useDest = false;
  296. nProfiles = profileList.length;
  297. nTransforms = nProfiles;
  298. if ((nProfiles == 0) || (srcProfile != profileList[0])) {
  299. nTransforms += 1;
  300. useSrc = true;
  301. }
  302. if ((nProfiles == 0) || (destProfile != profileList[nProfiles - 1]) ||
  303. (nTransforms < 2)) {
  304. nTransforms += 1;
  305. useDest = true;
  306. }
  307. /* make the profile list */
  308. theProfiles = new ICC_Profile[nTransforms]; /* the list of profiles
  309. for this Op */
  310. int idx = 0;
  311. if (useSrc) {
  312. /* insert source as first profile */
  313. theProfiles[idx++] = srcProfile;
  314. }
  315. for (i1 = 0; i1 < nProfiles; i1++) {
  316. /* insert profiles defined in this Op */
  317. theProfiles[idx++] = profileList [i1];
  318. }
  319. if (useDest) {
  320. /* insert dest as last profile */
  321. theProfiles[idx] = destProfile;
  322. }
  323. /* make the transform list */
  324. theTransforms = new ICC_Transform [nTransforms];
  325. /* initialize transform get loop */
  326. if (theProfiles[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
  327. /* if first profile is a printer
  328. render as colorimetric */
  329. renderState = ICC_Profile.icRelativeColorimetric;
  330. }
  331. else {
  332. renderState = ICC_Profile.icPerceptual; /* render any other
  333. class perceptually */
  334. }
  335. whichTrans = ICC_Transform.In;
  336. /* get the transforms from each profile */
  337. for (i1 = 0; i1 < nTransforms; i1++) {
  338. if (i1 == nTransforms -1) { /* last profile? */
  339. whichTrans = ICC_Transform.Out; /* get output transform */
  340. }
  341. else { /* check for abstract profile */
  342. if ((whichTrans == ICC_Transform.Simulation) &&
  343. (theProfiles[i1].getProfileClass () ==
  344. ICC_Profile.CLASS_ABSTRACT)) {
  345. renderState = ICC_Profile.icPerceptual;
  346. whichTrans = ICC_Transform.In;
  347. }
  348. }
  349. theTransforms[i1] = new ICC_Transform (theProfiles[i1],
  350. renderState, whichTrans);
  351. /* get this profile's rendering intent to select transform
  352. from next profile */
  353. renderState = getRenderingIntent(theProfiles[i1]);
  354. /* "middle" profiles use simulation transform */
  355. whichTrans = ICC_Transform.Simulation;
  356. }
  357. /* make the net transform */
  358. thisTransform = new ICC_Transform (theTransforms);
  359. /* update corresponding source and dest profiles */
  360. thisSrcProfile = srcProfile;
  361. thisDestProfile = destProfile;
  362. }
  363. /**
  364. * ColorConverts the image data in the source Raster.
  365. * If the destination Raster is null, a new Raster will be created.
  366. * The number of bands in the source and destination Rasters must
  367. * meet the requirements explained above. The constructor used to
  368. * create this ColorConvertOp must have provided enough information
  369. * to define both source and destination color spaces. See above.
  370. * Otherwise, an exception is thrown.
  371. * @param src the source <code>Raster</code> to be converted
  372. * @param dest the destination <code>WritableRaster</code>,
  373. * or <code>null</code>
  374. * @return <code>dest</code> color converted from <code>src</code>
  375. * or a new, converted <code>WritableRaster</code>
  376. * if <code>dest</code> is <code>null</code>
  377. * @exception IllegalArgumentException if the number of source or
  378. * destination bands is incorrect, the source or destination
  379. * color spaces are undefined, or this op was constructed
  380. * with one of the constructors that applies only to
  381. * operations on BufferedImages.
  382. */
  383. public final WritableRaster filter (Raster src, WritableRaster dest) {
  384. if (CSList != null) {
  385. /* non-ICC case */
  386. return nonICCRasterFilter(src, dest);
  387. }
  388. int nProfiles = profileList.length;
  389. if (nProfiles < 2) {
  390. throw new IllegalArgumentException(
  391. "Source or Destination ColorSpace is undefined");
  392. }
  393. if (src.getNumBands() != profileList[0].getNumComponents()) {
  394. throw new IllegalArgumentException(
  395. "Numbers of source Raster bands and source color space " +
  396. "components do not match");
  397. }
  398. if (dest == null) {
  399. dest = createCompatibleDestRaster(src);
  400. }
  401. else {
  402. if (src.getHeight() != dest.getHeight() ||
  403. src.getWidth() != dest.getWidth()) {
  404. throw new IllegalArgumentException(
  405. "Width or height of Rasters do not match");
  406. }
  407. if (dest.getNumBands() !=
  408. profileList[nProfiles-1].getNumComponents()) {
  409. throw new IllegalArgumentException(
  410. "Numbers of destination Raster bands and destination " +
  411. "color space components do not match");
  412. }
  413. }
  414. /* make a new transform if needed */
  415. if (thisRasterTransform == null) {
  416. int i1, whichTrans, renderState;
  417. ICC_Transform[] theTransforms;
  418. /* make the transform list */
  419. theTransforms = new ICC_Transform [nProfiles];
  420. /* initialize transform get loop */
  421. if (profileList[0].getProfileClass() == ICC_Profile.CLASS_OUTPUT) {
  422. /* if first profile is a printer
  423. render as colorimetric */
  424. renderState = ICC_Profile.icRelativeColorimetric;
  425. }
  426. else {
  427. renderState = ICC_Profile.icPerceptual; /* render any other
  428. class perceptually */
  429. }
  430. whichTrans = ICC_Transform.In;
  431. /* get the transforms from each profile */
  432. for (i1 = 0; i1 < nProfiles; i1++) {
  433. if (i1 == nProfiles -1) { /* last profile? */
  434. whichTrans = ICC_Transform.Out; /* get output transform */
  435. }
  436. else { /* check for abstract profile */
  437. if ((whichTrans == ICC_Transform.Simulation) &&
  438. (profileList[i1].getProfileClass () ==
  439. ICC_Profile.CLASS_ABSTRACT)) {
  440. renderState = ICC_Profile.icPerceptual;
  441. whichTrans = ICC_Transform.In;
  442. }
  443. }
  444. theTransforms[i1] = new ICC_Transform (profileList[i1],
  445. renderState, whichTrans);
  446. /* get this profile's rendering intent to select transform
  447. from next profile */
  448. renderState = getRenderingIntent(profileList[i1]);
  449. /* "middle" profiles use simulation transform */
  450. whichTrans = ICC_Transform.Simulation;
  451. }
  452. /* make the net transform */
  453. thisRasterTransform = new ICC_Transform (theTransforms);
  454. }
  455. int srcTransferType = src.getTransferType();
  456. int dstTransferType = dest.getTransferType();
  457. if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
  458. (srcTransferType == DataBuffer.TYPE_DOUBLE) ||
  459. (dstTransferType == DataBuffer.TYPE_FLOAT) ||
  460. (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
  461. if (srcMinVals == null) {
  462. getMinMaxValsFromProfiles(profileList[0],
  463. profileList[nProfiles-1]);
  464. }
  465. /* color convert the raster */
  466. thisRasterTransform.colorConvert(src, dest,
  467. srcMinVals, srcMaxVals,
  468. dstMinVals, dstMaxVals);
  469. } else {
  470. /* color convert the raster */
  471. thisRasterTransform.colorConvert(src, dest);
  472. }
  473. return dest;
  474. }
  475. /**
  476. * Returns the bounding box of the destination, given this source.
  477. * Note that this will be the same as the the bounding box of the
  478. * source.
  479. * @param src the source <code>BufferedImage</code>
  480. * @return a <code>Rectangle2D</code> that is the bounding box
  481. * of the destination, given the specified <code>src</code>
  482. */
  483. public final Rectangle2D getBounds2D (BufferedImage src) {
  484. return getBounds2D(src.getRaster());
  485. }
  486. /**
  487. * Returns the bounding box of the destination, given this source.
  488. * Note that this will be the same as the the bounding box of the
  489. * source.
  490. * @param src the source <code>Raster</code>
  491. * @return a <code>Rectangle2D</code> that is the bounding box
  492. * of the destination, given the specified <code>src</code>
  493. */
  494. public final Rectangle2D getBounds2D (Raster src) {
  495. /* return new Rectangle (src.getXOffset(),
  496. src.getYOffset(),
  497. src.getWidth(), src.getHeight()); */
  498. return src.getBounds();
  499. }
  500. /**
  501. * Creates a zeroed destination image with the correct size and number of
  502. * bands, given this source.
  503. * @param src Source image for the filter operation.
  504. * @param destCM ColorModel of the destination. If null, an
  505. * appropriate ColorModel will be used.
  506. * @return a <code>BufferedImage</code> with the correct size and
  507. * number of bands from the specified <code>src</code>.
  508. * @throws IllegalArgumentException if <code>destCM</code> is
  509. * <code>null</code> and this <code>ColorConvertOp</code> was
  510. * created without any <code>ICC_Profile</code> or
  511. * <code>ColorSpace</code> defined for the destination
  512. */
  513. public BufferedImage createCompatibleDestImage (BufferedImage src,
  514. ColorModel destCM) {
  515. ColorSpace cs = null;;
  516. if (destCM == null) {
  517. if (CSList == null) {
  518. /* ICC case */
  519. int nProfiles = profileList.length;
  520. if (nProfiles == 0) {
  521. throw new IllegalArgumentException(
  522. "Destination ColorSpace is undefined");
  523. }
  524. ICC_Profile destProfile = profileList[nProfiles - 1];
  525. cs = new ICC_ColorSpace(destProfile);
  526. } else {
  527. /* non-ICC case */
  528. int nSpaces = CSList.length;
  529. cs = CSList[nSpaces - 1];
  530. }
  531. }
  532. return createCompatibleDestImage(src, destCM, cs);
  533. }
  534. private BufferedImage createCompatibleDestImage(BufferedImage src,
  535. ColorModel destCM,
  536. ColorSpace destCS) {
  537. BufferedImage image;
  538. if (destCM == null) {
  539. ColorModel srcCM = src.getColorModel();
  540. int nbands = destCS.getNumComponents();
  541. boolean hasAlpha = srcCM.hasAlpha();
  542. if (hasAlpha) {
  543. nbands += 1;
  544. }
  545. int[] nbits = new int[nbands];
  546. for (int i = 0; i < nbands; i++) {
  547. nbits[i] = 8;
  548. }
  549. destCM = new ComponentColorModel(destCS, nbits, hasAlpha,
  550. srcCM.isAlphaPremultiplied(),
  551. srcCM.getTransparency(),
  552. DataBuffer.TYPE_BYTE);
  553. }
  554. int w = src.getWidth();
  555. int h = src.getHeight();
  556. image = new BufferedImage(destCM,
  557. destCM.createCompatibleWritableRaster(w, h),
  558. destCM.isAlphaPremultiplied(), null);
  559. return image;
  560. }
  561. /**
  562. * Creates a zeroed destination Raster with the correct size and number of
  563. * bands, given this source.
  564. * @param src the specified <code>Raster</code>
  565. * @return a <code>WritableRaster</code> with the correct size and number
  566. * of bands from the specified <code>src</code>
  567. * @throws IllegalArgumentException if this <code>ColorConvertOp</code>
  568. * was created without sufficient information to define the
  569. * <code>dst</code> and <code>src</code> color spaces
  570. */
  571. public WritableRaster createCompatibleDestRaster (Raster src) {
  572. int ncomponents;
  573. if (CSList != null) {
  574. /* non-ICC case */
  575. if (CSList.length != 2) {
  576. throw new IllegalArgumentException(
  577. "Destination ColorSpace is undefined");
  578. }
  579. ncomponents = CSList[1].getNumComponents();
  580. } else {
  581. /* ICC case */
  582. int nProfiles = profileList.length;
  583. if (nProfiles < 2) {
  584. throw new IllegalArgumentException(
  585. "Destination ColorSpace is undefined");
  586. }
  587. ncomponents = profileList[nProfiles-1].getNumComponents();
  588. }
  589. WritableRaster dest =
  590. Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
  591. src.getWidth(),
  592. src.getHeight(),
  593. ncomponents,
  594. new Point(src.getMinX(), src.getMinY()));
  595. return dest;
  596. }
  597. /**
  598. * Returns the location of the destination point given a
  599. * point in the source. If <code>dstPt</code> is non-null,
  600. * it will be used to hold the return value. Note that
  601. * for this class, the destination point will be the same
  602. * as the source point.
  603. * @param srcPt the specified source <code>Point2D</code>
  604. * @param dstPt the destination <code>Point2D</code>
  605. * @return <code>dstPt</code> after setting its location to be
  606. * the same as <code>srcPt</code>
  607. */
  608. public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
  609. if (dstPt == null) {
  610. dstPt = new Point2D.Float();
  611. }
  612. dstPt.setLocation(srcPt.getX(), srcPt.getY());
  613. return dstPt;
  614. }
  615. /**
  616. * Returns the RenderingIntent from the specified ICC Profile.
  617. */
  618. private int getRenderingIntent (ICC_Profile profile) {
  619. byte[] header = profile.getData(ICC_Profile.icSigHead);
  620. int index = ICC_Profile.icHdrRenderingIntent;
  621. return (((header[index] & 0xff) << 24) |
  622. ((header[index+1] & 0xff) << 16) |
  623. ((header[index+2] & 0xff) << 8) |
  624. (header[index+3] & 0xff));
  625. }
  626. /**
  627. * Returns the rendering hints used by this op.
  628. * @return the <code>RenderingHints</code> object of this
  629. * <code>ColorConvertOp</code>
  630. */
  631. public final RenderingHints getRenderingHints() {
  632. return hints;
  633. }
  634. private final BufferedImage nonICCBIFilter(BufferedImage src,
  635. ColorSpace srcColorSpace,
  636. BufferedImage dst,
  637. ColorSpace dstColorSpace) {
  638. int w = src.getWidth();
  639. int h = src.getHeight();
  640. ICC_ColorSpace ciespace =
  641. (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
  642. if (dst == null) {
  643. dst = createCompatibleDestImage(src, null);
  644. dstColorSpace = dst.getColorModel().getColorSpace();
  645. } else {
  646. if ((h != dst.getHeight()) || (w != dst.getWidth())) {
  647. throw new IllegalArgumentException(
  648. "Width or height of BufferedImages do not match");
  649. }
  650. }
  651. Raster srcRas = src.getRaster();
  652. WritableRaster dstRas = dst.getRaster();
  653. ColorModel srcCM = src.getColorModel();
  654. ColorModel dstCM = dst.getColorModel();
  655. int srcNumComp = srcCM.getNumColorComponents();
  656. int dstNumComp = dstCM.getNumColorComponents();
  657. boolean dstHasAlpha = dstCM.hasAlpha();
  658. boolean needSrcAlpha = srcCM.hasAlpha() && dstHasAlpha;
  659. ColorSpace[] list;
  660. if ((CSList == null) && (profileList.length != 0)) {
  661. /* possible non-ICC src, some profiles, possible non-ICC dst */
  662. boolean nonICCSrc, nonICCDst;
  663. ICC_Profile srcProfile, dstProfile;
  664. if (!(srcColorSpace instanceof ICC_ColorSpace)) {
  665. nonICCSrc = true;
  666. srcProfile = ciespace.getProfile();
  667. } else {
  668. nonICCSrc = false;
  669. srcProfile = ((ICC_ColorSpace) srcColorSpace).getProfile();
  670. }
  671. if (!(dstColorSpace instanceof ICC_ColorSpace)) {
  672. nonICCDst = true;
  673. dstProfile = ciespace.getProfile();
  674. } else {
  675. nonICCDst = false;
  676. dstProfile = ((ICC_ColorSpace) dstColorSpace).getProfile();
  677. }
  678. /* make a new transform if needed */
  679. if ((thisTransform == null) || (thisSrcProfile != srcProfile) ||
  680. (thisDestProfile != dstProfile) ) {
  681. updateBITransform(srcProfile, dstProfile);
  682. }
  683. // process per scanline
  684. float maxNum = 65535.0f; // use 16-bit precision in CMM
  685. ColorSpace cs;
  686. int iccSrcNumComp;
  687. if (nonICCSrc) {
  688. cs = ciespace;
  689. iccSrcNumComp = 3;
  690. } else {
  691. cs = srcColorSpace;
  692. iccSrcNumComp = srcNumComp;
  693. }
  694. float[] srcMinVal = new float[iccSrcNumComp];
  695. float[] srcInvDiffMinMax = new float[iccSrcNumComp];
  696. for (int i = 0; i < srcNumComp; i++) {
  697. srcMinVal[i] = cs.getMinValue(i);
  698. srcInvDiffMinMax[i] = maxNum / (cs.getMaxValue(i) - srcMinVal[i]);
  699. }
  700. int iccDstNumComp;
  701. if (nonICCDst) {
  702. cs = ciespace;
  703. iccDstNumComp = 3;
  704. } else {
  705. cs = dstColorSpace;
  706. iccDstNumComp = dstNumComp;
  707. }
  708. float[] dstMinVal = new float[iccDstNumComp];
  709. float[] dstDiffMinMax = new float[iccDstNumComp];
  710. for (int i = 0; i < dstNumComp; i++) {
  711. dstMinVal[i] = cs.getMinValue(i);
  712. dstDiffMinMax[i] = (cs.getMaxValue(i) - dstMinVal[i]) / maxNum;
  713. }
  714. float[] dstColor;
  715. if (dstHasAlpha) {
  716. int size = ((dstNumComp + 1) > 3) ? (dstNumComp + 1) : 3;
  717. dstColor = new float[size];
  718. } else {
  719. int size = (dstNumComp > 3) ? dstNumComp : 3;
  720. dstColor = new float[size];
  721. }
  722. short[] srcLine = new short[w * iccSrcNumComp];
  723. short[] dstLine = new short[w * iccDstNumComp];
  724. Object pixel;
  725. float[] color;
  726. float[] alpha = null;
  727. if (needSrcAlpha) {
  728. alpha = new float[w];
  729. }
  730. int idx;
  731. // process each scanline
  732. for (int y = 0; y < h; y++) {
  733. // convert src scanline
  734. pixel = null;
  735. color = null;
  736. idx = 0;
  737. for (int x = 0; x < w; x++) {
  738. pixel = srcRas.getDataElements(x, y, pixel);
  739. color = srcCM.getNormalizedComponents(pixel, color, 0);
  740. if (needSrcAlpha) {
  741. alpha[x] = color[srcNumComp];
  742. }
  743. if (nonICCSrc) {
  744. color = srcColorSpace.toCIEXYZ(color);
  745. }
  746. for (int i = 0; i < iccSrcNumComp; i++) {
  747. srcLine[idx++] = (short)
  748. ((color[i] - srcMinVal[i]) * srcInvDiffMinMax[i] +
  749. 0.5f);
  750. }
  751. }
  752. // color convert srcLine to dstLine
  753. thisTransform.colorConvert(srcLine, dstLine);
  754. // convert dst scanline
  755. pixel = null;
  756. idx = 0;
  757. for (int x = 0; x < w; x++) {
  758. for (int i = 0; i < iccDstNumComp; i++) {
  759. dstColor[i] = ((float) (dstLine[idx++] & 0xffff)) *
  760. dstDiffMinMax[i] + dstMinVal[i];
  761. }
  762. if (nonICCDst) {
  763. color = srcColorSpace.fromCIEXYZ(dstColor);
  764. for (int i = 0; i < dstNumComp; i++) {
  765. dstColor[i] = color[i];
  766. }
  767. }
  768. if (needSrcAlpha) {
  769. dstColor[dstNumComp] = alpha[x];
  770. } else if (dstHasAlpha) {
  771. dstColor[dstNumComp] = 1.0f;
  772. }
  773. pixel = dstCM.getDataElements(dstColor, 0, pixel);
  774. dstRas.setDataElements(x, y, pixel);
  775. }
  776. }
  777. } else {
  778. /* possible non-ICC src, possible CSList, possible non-ICC dst */
  779. // process per pixel
  780. int numCS;
  781. if (CSList == null) {
  782. numCS = 0;
  783. } else {
  784. numCS = CSList.length;
  785. }
  786. float[] dstColor;
  787. if (dstHasAlpha) {
  788. dstColor = new float[dstNumComp + 1];
  789. } else {
  790. dstColor = new float[dstNumComp];
  791. }
  792. Object spixel = null;
  793. Object dpixel = null;
  794. float[] color = null;
  795. float[] tmpColor;
  796. // process each pixel
  797. for (int y = 0; y < h; y++) {
  798. for (int x = 0; x < w; x++) {
  799. spixel = srcRas.getDataElements(x, y, spixel);
  800. color = srcCM.getNormalizedComponents(spixel, color, 0);
  801. tmpColor = srcColorSpace.toCIEXYZ(color);
  802. for (int i = 0; i < numCS; i++) {
  803. tmpColor = CSList[i].fromCIEXYZ(tmpColor);
  804. tmpColor = CSList[i].toCIEXYZ(tmpColor);
  805. }
  806. tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
  807. for (int i = 0; i < dstNumComp; i++) {
  808. dstColor[i] = tmpColor[i];
  809. }
  810. if (needSrcAlpha) {
  811. dstColor[dstNumComp] = color[srcNumComp];
  812. } else if (dstHasAlpha) {
  813. dstColor[dstNumComp] = 1.0f;
  814. }
  815. dpixel = dstCM.getDataElements(dstColor, 0, dpixel);
  816. dstRas.setDataElements(x, y, dpixel);
  817. }
  818. }
  819. }
  820. return dst;
  821. }
  822. /* color convert a Raster - handles byte, ushort, int, short, float,
  823. or double transferTypes */
  824. private final WritableRaster nonICCRasterFilter(Raster src,
  825. WritableRaster dst) {
  826. if (CSList.length != 2) {
  827. throw new IllegalArgumentException(
  828. "Destination ColorSpace is undefined");
  829. }
  830. if (src.getNumBands() != CSList[0].getNumComponents()) {
  831. throw new IllegalArgumentException(
  832. "Numbers of source Raster bands and source color space " +
  833. "components do not match");
  834. }
  835. if (dst == null) {
  836. dst = createCompatibleDestRaster(src);
  837. } else {
  838. if (src.getHeight() != dst.getHeight() ||
  839. src.getWidth() != dst.getWidth()) {
  840. throw new IllegalArgumentException(
  841. "Width or height of Rasters do not match");
  842. }
  843. if (dst.getNumBands() != CSList[1].getNumComponents()) {
  844. throw new IllegalArgumentException(
  845. "Numbers of destination Raster bands and destination " +
  846. "color space components do not match");
  847. }
  848. }
  849. if (srcMinVals == null) {
  850. getMinMaxValsFromColorSpaces(CSList[0], CSList[1]);
  851. }
  852. SampleModel srcSM = src.getSampleModel();
  853. SampleModel dstSM = dst.getSampleModel();
  854. boolean srcIsFloat, dstIsFloat;
  855. int srcTransferType = src.getTransferType();
  856. int dstTransferType = dst.getTransferType();
  857. if ((srcTransferType == DataBuffer.TYPE_FLOAT) ||
  858. (srcTransferType == DataBuffer.TYPE_DOUBLE)) {
  859. srcIsFloat = true;
  860. } else {
  861. srcIsFloat = false;
  862. }
  863. if ((dstTransferType == DataBuffer.TYPE_FLOAT) ||
  864. (dstTransferType == DataBuffer.TYPE_DOUBLE)) {
  865. dstIsFloat = true;
  866. } else {
  867. dstIsFloat = false;
  868. }
  869. int w = src.getWidth();
  870. int h = src.getHeight();
  871. int srcNumBands = src.getNumBands();
  872. int dstNumBands = dst.getNumBands();
  873. float[] srcScaleFactor = null;
  874. float[] dstScaleFactor = null;
  875. if (!srcIsFloat) {
  876. srcScaleFactor = new float[srcNumBands];
  877. for (int i = 0; i < srcNumBands; i++) {
  878. if (srcTransferType == DataBuffer.TYPE_SHORT) {
  879. srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
  880. 32767.0f;
  881. } else {
  882. srcScaleFactor[i] = (srcMaxVals[i] - srcMinVals[i]) /
  883. ((float) ((1 << srcSM.getSampleSize(i)) - 1));
  884. }
  885. }
  886. }
  887. if (!dstIsFloat) {
  888. dstScaleFactor = new float[dstNumBands];
  889. for (int i = 0; i < dstNumBands; i++) {
  890. if (dstTransferType == DataBuffer.TYPE_SHORT) {
  891. dstScaleFactor[i] = 32767.0f /
  892. (dstMaxVals[i] - dstMinVals[i]);
  893. } else {
  894. dstScaleFactor[i] =
  895. ((float) ((1 << dstSM.getSampleSize(i)) - 1)) /
  896. (dstMaxVals[i] - dstMinVals[i]);
  897. }
  898. }
  899. }
  900. int ys = src.getMinY();
  901. int yd = dst.getMinY();
  902. int xs, xd;
  903. float sample;
  904. float[] color = new float[srcNumBands];
  905. float[] tmpColor;
  906. ColorSpace srcColorSpace = CSList[0];
  907. ColorSpace dstColorSpace = CSList[1];
  908. // process each pixel
  909. for (int y = 0; y < h; y++, ys++, yd++) {
  910. // get src scanline
  911. xs = src.getMinX();
  912. xd = dst.getMinX();
  913. for (int x = 0; x < w; x++, xs++, xd++) {
  914. for (int i = 0; i < srcNumBands; i++) {
  915. sample = src.getSampleFloat(xs, ys, i);
  916. if (!srcIsFloat) {
  917. sample = sample * srcScaleFactor[i] + srcMinVals[i];
  918. }
  919. color[i] = sample;
  920. }
  921. tmpColor = srcColorSpace.toCIEXYZ(color);
  922. tmpColor = dstColorSpace.fromCIEXYZ(tmpColor);
  923. for (int i = 0; i < dstNumBands; i++) {
  924. sample = tmpColor[i];
  925. if (!dstIsFloat) {
  926. sample = (sample - dstMinVals[i]) * dstScaleFactor[i];
  927. }
  928. dst.setSample(xd, yd, i, sample);
  929. }
  930. }
  931. }
  932. return dst;
  933. }
  934. private void getMinMaxValsFromProfiles(ICC_Profile srcProfile,
  935. ICC_Profile dstProfile) {
  936. int type = srcProfile.getColorSpaceType();
  937. int nc = srcProfile.getNumComponents();
  938. srcMinVals = new float[nc];
  939. srcMaxVals = new float[nc];
  940. setMinMax(type, nc, srcMinVals, srcMaxVals);
  941. type = dstProfile.getColorSpaceType();
  942. nc = dstProfile.getNumComponents();
  943. dstMinVals = new float[nc];
  944. dstMaxVals = new float[nc];
  945. setMinMax(type, nc, dstMinVals, dstMaxVals);
  946. }
  947. private void setMinMax(int type, int nc, float[] minVals, float[] maxVals) {
  948. if (type == ColorSpace.TYPE_Lab) {
  949. minVals[0] = 0.0f; // L
  950. maxVals[0] = 100.0f;
  951. minVals[1] = -128.0f; // a
  952. maxVals[1] = 127.0f;
  953. minVals[2] = -128.0f; // b
  954. maxVals[2] = 127.0f;
  955. } else if (type == ColorSpace.TYPE_XYZ) {
  956. minVals[0] = minVals[1] = minVals[2] = 0.0f; // X, Y, Z
  957. maxVals[0] = maxVals[1] = maxVals[2] = 1.0f + (32767.0f 32768.0f);
  958. } else {
  959. for (int i = 0; i < nc; i++) {
  960. minVals[i] = 0.0f;
  961. maxVals[i] = 1.0f;
  962. }
  963. }
  964. }
  965. private void getMinMaxValsFromColorSpaces(ColorSpace srcCspace,
  966. ColorSpace dstCspace) {
  967. int nc = srcCspace.getNumComponents();
  968. srcMinVals = new float[nc];
  969. srcMaxVals = new float[nc];
  970. for (int i = 0; i < nc; i++) {
  971. srcMinVals[i] = srcCspace.getMinValue(i);
  972. srcMaxVals[i] = srcCspace.getMaxValue(i);
  973. }
  974. nc = dstCspace.getNumComponents();
  975. dstMinVals = new float[nc];
  976. dstMaxVals = new float[nc];
  977. for (int i = 0; i < nc; i++) {
  978. dstMinVals[i] = dstCspace.getMinValue(i);
  979. dstMaxVals[i] = dstCspace.getMaxValue(i);
  980. }
  981. }
  982. }