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.color.ColorSpace;
022: import java.awt.image.BandCombineOp;
023: import java.awt.image.BufferedImage;
024: import java.awt.image.ColorConvertOp;
025: import java.awt.image.ColorModel;
026: import java.awt.image.DataBuffer;
027: import java.awt.image.DataBufferInt;
028: import java.awt.image.Raster;
029: import java.awt.image.SampleModel;
030: import java.awt.image.SinglePixelPackedSampleModel;
031: import java.awt.image.WritableRaster;
032:
033: import org.apache.batik.ext.awt.image.GraphicsUtil;
034:
035: /**
036: * This function will tranform an image from any colorspace into a
037: * luminance image. The alpha channel if any will be copied to the
038: * new image.
039: *
040: * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
041: * @version $Id: Any2sRGBRed.java 478276 2006-11-22 18:33:37Z dvholten $ */
042: public class Any2sRGBRed extends AbstractRed {
043:
044: boolean srcIsLsRGB = false;
045:
046: /**
047: * Construct a luminace image from src.
048: *
049: * @param src The image to convert to a luminance image
050: */
051: public Any2sRGBRed(CachableRed src) {
052: super (src, src.getBounds(), fixColorModel(src),
053: fixSampleModel(src), src.getTileGridXOffset(), src
054: .getTileGridYOffset(), null);
055:
056: ColorModel srcCM = src.getColorModel();
057: if (srcCM == null)
058: return;
059: ColorSpace srcCS = srcCM.getColorSpace();
060: if (srcCS == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB))
061: srcIsLsRGB = true;
062: }
063:
064: public static boolean is_INT_PACK_COMP(SampleModel sm) {
065: if (!(sm instanceof SinglePixelPackedSampleModel))
066: return false;
067:
068: // Check transfer types
069: if (sm.getDataType() != DataBuffer.TYPE_INT)
070: return false;
071:
072: SinglePixelPackedSampleModel sppsm;
073: sppsm = (SinglePixelPackedSampleModel) sm;
074:
075: int[] masks = sppsm.getBitMasks();
076: if ((masks.length != 3) && (masks.length != 4))
077: return false;
078: if (masks[0] != 0x00ff0000)
079: return false;
080: if (masks[1] != 0x0000ff00)
081: return false;
082: if (masks[2] != 0x000000ff)
083: return false;
084: if ((masks.length == 4) && (masks[3] != 0xff000000))
085: return false;
086:
087: return true;
088: }
089:
090: /**
091: * Exponent for linear to sRGB convertion
092: */
093: private static final double GAMMA = 2.4;
094:
095: /**
096: * Lookup tables for RGB lookups. The linearToSRGBLut is used
097: * when noise values are considered to be on a linearScale. The
098: * linearToLinear table is used when the values are considered to
099: * be on the sRGB scale to begin with.
100: */
101: private static final int[] linearToSRGBLut = new int[256];
102: static {
103: final double scale = 1.0 / 255;
104: final double exp = 1.0 / GAMMA;
105: // System.out.print("L2S: ");
106: for (int i = 0; i < 256; i++) {
107: double value = i * scale;
108: if (value <= 0.0031308)
109: value *= 12.92;
110: else
111: value = 1.055 * Math.pow(value, exp) - 0.055;
112:
113: linearToSRGBLut[i] = (int) Math.round(value * 255.);
114: // System.out.print(linearToSRGBLut[i] + ",");
115: }
116: // System.out.println("");
117: }
118:
119: public static WritableRaster applyLut_INT(WritableRaster wr,
120: final int[] lut) {
121: SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel) wr
122: .getSampleModel();
123: DataBufferInt db = (DataBufferInt) wr.getDataBuffer();
124:
125: final int srcBase = (db.getOffset() + sm.getOffset(wr.getMinX()
126: - wr.getSampleModelTranslateX(), wr.getMinY()
127: - wr.getSampleModelTranslateY()));
128: // Access the pixel data array
129: final int[] pixels = db.getBankData()[0];
130: final int width = wr.getWidth();
131: final int height = wr.getHeight();
132: final int scanStride = sm.getScanlineStride();
133:
134: int end, pix;
135:
136: // For alpha premult we need to multiply all comps.
137: for (int y = 0; y < height; y++) {
138: int sp = srcBase + y * scanStride;
139: end = sp + width;
140:
141: while (sp < end) {
142: pix = pixels[sp];
143: pixels[sp] = ((pix & 0xFF000000)
144: | (lut[(pix >>> 16) & 0xFF] << 16)
145: | (lut[(pix >>> 8) & 0xFF] << 8) | (lut[(pix) & 0xFF]));
146: sp++;
147: }
148: }
149:
150: return wr;
151: }
152:
153: public WritableRaster copyData(WritableRaster wr) {
154:
155: // Get my source.
156: CachableRed src = (CachableRed) getSources().get(0);
157: ColorModel srcCM = src.getColorModel();
158: SampleModel srcSM = src.getSampleModel();
159:
160: // Fast case, Linear SRGB source, INT Pack writable raster...
161: if (srcIsLsRGB && is_INT_PACK_COMP(wr.getSampleModel())) {
162: src.copyData(wr);
163: if (srcCM.hasAlpha())
164: GraphicsUtil.coerceData(wr, srcCM, false);
165: applyLut_INT(wr, linearToSRGBLut);
166: return wr;
167: }
168:
169: if (srcCM == null) {
170: // We don't really know much about this source, let's
171: // guess based on the number of bands...
172:
173: float[][] matrix = null;
174: switch (srcSM.getNumBands()) {
175: case 1:
176: matrix = new float[3][1];
177: matrix[0][0] = 1; // Red
178: matrix[1][0] = 1; // Grn
179: matrix[2][0] = 1; // Blu
180: break;
181: case 2:
182: matrix = new float[4][2];
183: matrix[0][0] = 1; // Red
184: matrix[1][0] = 1; // Grn
185: matrix[3][0] = 1; // Blu
186: matrix[3][1] = 1; // Alpha
187: break;
188: case 3:
189: matrix = new float[3][3];
190: matrix[0][0] = 1; // Red
191: matrix[1][1] = 1; // Grn
192: matrix[2][2] = 1; // Blu
193: break;
194: default:
195: matrix = new float[4][srcSM.getNumBands()];
196: matrix[0][0] = 1; // Red
197: matrix[1][1] = 1; // Grn
198: matrix[2][2] = 1; // Blu
199: matrix[3][3] = 1; // Alpha
200: break;
201: }
202: Raster srcRas = src.getData(wr.getBounds());
203: BandCombineOp op = new BandCombineOp(matrix, null);
204: op.filter(srcRas, wr);
205: return wr;
206: }
207:
208: if (srcCM.getColorSpace() == ColorSpace
209: .getInstance(ColorSpace.CS_GRAY)) {
210:
211: // This is a little bit of a hack. There is only
212: // a linear grayscale ICC profile in the JDK so
213: // many things use this when the data _really_
214: // has sRGB gamma applied.
215: try {
216: float[][] matrix = null;
217: switch (srcSM.getNumBands()) {
218: case 1:
219: matrix = new float[3][1];
220: matrix[0][0] = 1; // Red
221: matrix[1][0] = 1; // Grn
222: matrix[2][0] = 1; // Blu
223: break;
224: case 2:
225: default:
226: matrix = new float[4][2];
227: matrix[0][0] = 1; // Red
228: matrix[1][0] = 1; // Grn
229: matrix[3][0] = 1; // Blu
230: matrix[4][1] = 1; // Alpha
231: break;
232: }
233: Raster srcRas = src.getData(wr.getBounds());
234: BandCombineOp op = new BandCombineOp(matrix, null);
235: op.filter(srcRas, wr);
236: } catch (Throwable t) {
237: t.printStackTrace();
238: }
239: return wr;
240: }
241:
242: ColorModel dstCM = getColorModel();
243: if (srcCM.getColorSpace() == dstCM.getColorSpace()) {
244: // No transform needed, just reformat data...
245: // System.out.println("Bypassing");
246:
247: if (is_INT_PACK_COMP(srcSM))
248: src.copyData(wr);
249: else
250: GraphicsUtil.copyData(src.getData(wr.getBounds()), wr);
251:
252: return wr;
253: }
254:
255: Raster srcRas = src.getData(wr.getBounds());
256: WritableRaster srcWr = (WritableRaster) srcRas;
257:
258: // Divide out alpha if we have it. We need to do this since
259: // the color convert may not be a linear operation which may
260: // lead to out of range values.
261: ColorModel srcBICM = srcCM;
262: if (srcCM.hasAlpha())
263: srcBICM = GraphicsUtil.coerceData(srcWr, srcCM, false);
264:
265: BufferedImage srcBI, dstBI;
266: srcBI = new BufferedImage(srcBICM, srcWr
267: .createWritableTranslatedChild(0, 0), false, null);
268:
269: // System.out.println("src: " + srcBI.getWidth() + "x" +
270: // srcBI.getHeight());
271:
272: ColorConvertOp op = new ColorConvertOp(dstCM.getColorSpace(),
273: null);
274: dstBI = op.filter(srcBI, null);
275:
276: // System.out.println("After filter:");
277:
278: WritableRaster wr00 = wr.createWritableTranslatedChild(0, 0);
279: for (int i = 0; i < dstCM.getColorSpace().getNumComponents(); i++)
280: copyBand(dstBI.getRaster(), i, wr00, i);
281:
282: if (dstCM.hasAlpha())
283: copyBand(srcWr, srcSM.getNumBands() - 1, wr,
284: getSampleModel().getNumBands() - 1);
285: return wr;
286: }
287:
288: /**
289: * This function 'fixes' the source's color model. Right now
290: * it just selects if it should have one or two bands based on
291: * if the source had an alpha channel.
292: */
293: protected static ColorModel fixColorModel(CachableRed src) {
294: ColorModel cm = src.getColorModel();
295: if (cm != null) {
296: if (cm.hasAlpha())
297: return GraphicsUtil.sRGB_Unpre;
298:
299: return GraphicsUtil.sRGB;
300: } else {
301: // No ColorModel so try to make some intelligent
302: // decisions based just on the number of bands...
303: // 1 bands -> replicated into RGB
304: // 2 bands -> Band 0 replicated into RGB & Band 1 -> alpha premult
305: // 3 bands -> sRGB (not-linear?)
306: // 4 bands -> sRGB premult (not-linear?)
307: SampleModel sm = src.getSampleModel();
308:
309: switch (sm.getNumBands()) {
310: case 1:
311: return GraphicsUtil.sRGB;
312: case 2:
313: return GraphicsUtil.sRGB_Unpre;
314: case 3:
315: return GraphicsUtil.sRGB;
316: }
317: return GraphicsUtil.sRGB_Unpre;
318: }
319: }
320:
321: /**
322: * This function 'fixes' the source's sample model.
323: * Right now it just selects if it should have 3 or 4 bands
324: * based on if the source had an alpha channel.
325: */
326: protected static SampleModel fixSampleModel(CachableRed src) {
327: SampleModel sm = src.getSampleModel();
328: ColorModel cm = src.getColorModel();
329:
330: boolean alpha = false;
331:
332: if (cm != null)
333: alpha = cm.hasAlpha();
334: else {
335: switch (sm.getNumBands()) {
336: case 1:
337: case 3:
338: alpha = false;
339: break;
340: default:
341: alpha = true;
342: break;
343: }
344: }
345: if (alpha)
346: return new SinglePixelPackedSampleModel(
347: DataBuffer.TYPE_INT, sm.getWidth(), sm.getHeight(),
348: new int[] { 0xFF0000, 0xFF00, 0xFF, 0xFF000000 });
349: else
350: return new SinglePixelPackedSampleModel(
351: DataBuffer.TYPE_INT, sm.getWidth(), sm.getHeight(),
352: new int[] { 0xFF0000, 0xFF00, 0xFF });
353: }
354: }
|