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