001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.ext.awt.image.rendered;
020:
021: import java.awt.Graphics2D;
022: import java.awt.Point;
023: import java.awt.RenderingHints;
024: import java.awt.Transparency;
025: import java.awt.color.ColorSpace;
026: import java.awt.image.BandedSampleModel;
027: import java.awt.image.BufferedImage;
028: import java.awt.image.ColorConvertOp;
029: import java.awt.image.ColorModel;
030: import java.awt.image.ComponentColorModel;
031: import java.awt.image.DataBuffer;
032: import java.awt.image.DataBufferByte;
033: import java.awt.image.DirectColorModel;
034: import java.awt.image.Raster;
035: import java.awt.image.RenderedImage;
036: import java.awt.image.WritableRaster;
037:
038: import org.apache.batik.ext.awt.color.ICCColorSpaceExt;
039:
040: /**
041: * This implementation of rendered image forces a color profile
042: * on its source
043: *
044: * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
045: * @version $Id: ProfileRed.java 504084 2007-02-06 11:24:46Z dvholten $
046: */
047: public class ProfileRed extends AbstractRed {
048: private static final ColorSpace sRGBCS = ColorSpace
049: .getInstance(ColorSpace.CS_sRGB);
050: private static final ColorModel sRGBCM = new DirectColorModel(
051: sRGBCS, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000,
052: false, DataBuffer.TYPE_INT);
053:
054: private ICCColorSpaceExt colorSpace;
055:
056: /**
057: * @param src Images on which the input ColorSpace should
058: * be forced
059: * @param colorSpace colorSpace that should be forced on the
060: * source
061: */
062: public ProfileRed(CachableRed src, ICCColorSpaceExt colorSpace) {
063: this .colorSpace = colorSpace;
064:
065: init(src, src.getBounds(), sRGBCM, sRGBCM
066: .createCompatibleSampleModel(src.getWidth(), src
067: .getHeight()), src.getTileGridXOffset(), src
068: .getTileGridYOffset(), null);
069:
070: }
071:
072: public CachableRed getSource() {
073: return (CachableRed) getSources().get(0);
074: }
075:
076: /**
077: * This method will turn the input image in an sRGB image as follows.
078: * If there is no colorSpace defined, then the input image is
079: * simply converted to singlePixelPacked sRGB if needed.
080: * If there is a colorSpace defined, the the image data is 'interpreted'
081: * as being in that space, instead of that of the image's colorSpace.
082: *
083: * Here is how the input image is processed:
084: * a. It is converted to using a ComponentColorModel
085: * b. Its data is extracted, ignoring it's ColorSpace
086: * c. A new ComponentColorModel is built for the replacing colorSpace
087: * Note that if the number of components in the input image and
088: * the number of components in the replacing ColorSpace do not
089: * match, it is not possible to apply the conversion.
090: * d. A new BufferedImage is built, using the new
091: * ComponentColorModel and the data from the original image
092: * converted to the ComponentColorModel built in a. The alpha
093: * channel is excluded from that new BufferedImage.
094: * e. The BufferedImage created in d. is converted to sRGB using
095: * ColorConvertOp
096: * f. The alpha channel information is integrated back into the image.
097: *
098: * IMPORTANT NOTE: The code uses a BandedSampleModel in c.) and
099: * d.) and discard the alpha channel during the color conversions
100: * (it is restored in f.)), because of bugs in the interleaved
101: * model with alpha. The BandedSampleModel did not cause any bug
102: * as of JDK 1.3.
103: */
104: public WritableRaster copyData(WritableRaster argbWR) {
105: try {
106: RenderedImage img = getSource();
107:
108: /**
109: * Check that the number of color components match in the input
110: * image and in the replacing profile.
111: */
112: ColorModel imgCM = img.getColorModel();
113: ColorSpace imgCS = imgCM.getColorSpace();
114: int nImageComponents = imgCS.getNumComponents();
115: int nProfileComponents = colorSpace.getNumComponents();
116: if (nImageComponents != nProfileComponents) {
117: // Should we go in error???? Here we simply trace an error
118: // and return null
119: System.err
120: .println("Input image and associated color profile have"
121: + " mismatching number of color components: conversion is not possible");
122: return argbWR;
123: }
124:
125: /**
126: * Get the data from the source for the requested region
127: */
128: int w = argbWR.getWidth();
129: int h = argbWR.getHeight();
130: int minX = argbWR.getMinX();
131: int minY = argbWR.getMinY();
132: WritableRaster srcWR = imgCM
133: .createCompatibleWritableRaster(w, h);
134: srcWR = srcWR.createWritableTranslatedChild(minX, minY);
135: img.copyData(srcWR);
136:
137: /**
138: * If the source data is not a ComponentColorModel using a
139: * BandedSampleModel, do the conversion now.
140: */
141: if (!(imgCM instanceof ComponentColorModel)
142: || !(img.getSampleModel() instanceof BandedSampleModel)
143: || (imgCM.hasAlpha() && imgCM
144: .isAlphaPremultiplied())) {
145: ComponentColorModel imgCompCM = new ComponentColorModel(
146: imgCS, // Same ColorSpace as img
147: imgCM.getComponentSize(), // Number of bits/comp
148: imgCM.hasAlpha(), // Same alpha as img
149: false, // unpremult alpha (so we can remove it next).
150: imgCM.getTransparency(), // Same trans as img
151: DataBuffer.TYPE_BYTE); // 8 bit/component.
152:
153: WritableRaster wr = Raster.createBandedRaster(
154: DataBuffer.TYPE_BYTE, argbWR.getWidth(), argbWR
155: .getHeight(), imgCompCM
156: .getNumComponents(), new Point(0, 0));
157:
158: BufferedImage imgComp = new BufferedImage(imgCompCM,
159: wr, imgCompCM.isAlphaPremultiplied(), null);
160:
161: BufferedImage srcImg = new BufferedImage(imgCM, srcWR
162: .createWritableTranslatedChild(0, 0), imgCM
163: .isAlphaPremultiplied(), null);
164:
165: Graphics2D g = imgComp.createGraphics();
166: g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
167: RenderingHints.VALUE_COLOR_RENDER_QUALITY);
168: g.drawImage(srcImg, 0, 0, null);
169: img = imgComp;
170: imgCM = imgCompCM;
171: srcWR = wr.createWritableTranslatedChild(minX, minY);
172: }
173:
174: /**
175: * Now, the input image is using a component color
176: * model. We can therefore create an image with the new
177: * profile, using a ComponentColorModel as well, because
178: * we know the number of components match (this was
179: * checked at the begining of this routine). */
180: ComponentColorModel newCM = new ComponentColorModel(
181: colorSpace, // **** New ColorSpace ****
182: imgCM.getComponentSize(), // Array of number of bits per components
183: false, // No alpa
184: false, // Not premultiplied
185: Transparency.OPAQUE, // No transparency
186: DataBuffer.TYPE_BYTE); // 8 Bits
187:
188: // Build a raster with bands 0, 1 and 2 of img's raster
189: DataBufferByte data = (DataBufferByte) srcWR
190: .getDataBuffer();
191: srcWR = Raster.createBandedRaster(data, argbWR.getWidth(),
192: argbWR.getHeight(), argbWR.getWidth(), new int[] {
193: 0, 1, 2 }, new int[] { 0, 0, 0 },
194: new Point(0, 0));
195: BufferedImage newImg = new BufferedImage(newCM, srcWR,
196: newCM.isAlphaPremultiplied(), null);
197:
198: /**
199: * Now, convert the image to sRGB
200: */
201: ComponentColorModel sRGBCompCM = new ComponentColorModel(
202: ColorSpace.getInstance(ColorSpace.CS_sRGB),
203: new int[] { 8, 8, 8 }, false, false,
204: Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
205:
206: WritableRaster wr = Raster.createBandedRaster(
207: DataBuffer.TYPE_BYTE, argbWR.getWidth(), argbWR
208: .getHeight(),
209: sRGBCompCM.getNumComponents(), new Point(0, 0));
210:
211: BufferedImage sRGBImage = new BufferedImage(sRGBCompCM, wr,
212: false, null);
213: ColorConvertOp colorConvertOp = new ColorConvertOp(null);
214: colorConvertOp.filter(newImg, sRGBImage);
215:
216: /**
217: * Integrate alpha back into the image if there is any
218: */
219: if (imgCM.hasAlpha()) {
220: DataBufferByte rgbData = (DataBufferByte) wr
221: .getDataBuffer();
222: byte[][] imgBanks = data.getBankData();
223: byte[][] rgbBanks = rgbData.getBankData();
224:
225: byte[][] argbBanks = { rgbBanks[0], rgbBanks[1],
226: rgbBanks[2], imgBanks[3] };
227: DataBufferByte argbData = new DataBufferByte(argbBanks,
228: imgBanks[0].length);
229: srcWR = Raster.createBandedRaster(argbData, argbWR
230: .getWidth(), argbWR.getHeight(), argbWR
231: .getWidth(), new int[] { 0, 1, 2, 3 },
232: new int[] { 0, 0, 0, 0 }, new Point(0, 0));
233: sRGBCompCM = new ComponentColorModel(ColorSpace
234: .getInstance(ColorSpace.CS_sRGB), new int[] {
235: 8, 8, 8, 8 }, true, false,
236: Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
237: sRGBImage = new BufferedImage(sRGBCompCM, srcWR, false,
238: null);
239:
240: }
241:
242: /*BufferedImage result = new BufferedImage(img.getWidth(),
243: img.getHeight(),
244: BufferedImage.TYPE_INT_ARGB);*/
245: BufferedImage result = new BufferedImage(sRGBCM, argbWR
246: .createWritableTranslatedChild(0, 0), false, null);
247:
248: ///////////////////////////////////////////////
249: // BUG IN ColorConvertOp: The following breaks:
250: // colorConvertOp.filter(sRGBImage, result);
251: //
252: // Using Graphics2D instead....
253: ///////////////////////////////////////////////
254: Graphics2D g = result.createGraphics();
255: g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
256: RenderingHints.VALUE_COLOR_RENDER_QUALITY);
257: g.drawImage(sRGBImage, 0, 0, null);
258: g.dispose();
259:
260: return argbWR;
261: } catch (Exception e) {
262: e.printStackTrace();
263: throw new Error(e.getMessage());
264: }
265: }
266:
267: }
|