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