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