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: Any2LsRGBRed.java 499611 2007-01-24 23:17:50Z cam $ */
019:
020: package org.apache.xmlgraphics.image.rendered;
021:
022: import java.awt.Point;
023: import java.awt.Rectangle;
024: import java.awt.color.ColorSpace;
025: import java.awt.image.BandCombineOp;
026: import java.awt.image.BufferedImage;
027: import java.awt.image.ColorConvertOp;
028: import java.awt.image.ColorModel;
029: import java.awt.image.DataBuffer;
030: import java.awt.image.Raster;
031: import java.awt.image.SampleModel;
032: import java.awt.image.SinglePixelPackedSampleModel;
033: import java.awt.image.WritableRaster;
034:
035: import org.apache.xmlgraphics.image.GraphicsUtil;
036: import org.apache.xmlgraphics.image.rendered.AbstractRed;
037: import org.apache.xmlgraphics.image.rendered.Any2sRGBRed;
038: import org.apache.xmlgraphics.image.rendered.CachableRed;
039:
040: /**
041: * This function will tranform an image from any colorspace into a
042: * luminance image. The alpha channel if any will be copied to the
043: * new image.
044: *
045: * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
046: * @version $Id: Any2LsRGBRed.java 499611 2007-01-24 23:17:50Z cam $
047: */
048: public class Any2LsRGBRed extends AbstractRed {
049:
050: boolean srcIssRGB = false;
051:
052: /**
053: * Construct a luminace image from src.
054: *
055: * @param src The image to convert to a luminance image
056: */
057: public Any2LsRGBRed(CachableRed src) {
058: super (src, src.getBounds(), fixColorModel(src),
059: fixSampleModel(src), src.getTileGridXOffset(), src
060: .getTileGridYOffset(), null);
061:
062: ColorModel srcCM = src.getColorModel();
063: if (srcCM == null)
064: return;
065: ColorSpace srcCS = srcCM.getColorSpace();
066: if (srcCS == ColorSpace.getInstance(ColorSpace.CS_sRGB))
067: srcIssRGB = true;
068: }
069:
070: /**
071: * Gamma for linear to sRGB convertion
072: */
073: private static final double GAMMA = 2.4;
074: private static final double LFACT = 1.0 / 12.92;
075:
076: public static final double sRGBToLsRGB(double value) {
077: if (value <= 0.003928)
078: return value * LFACT;
079: return Math.pow((value + 0.055) / 1.055, GAMMA);
080: }
081:
082: /**
083: * Lookup tables for RGB lookups. The linearToSRGBLut is used
084: * when noise values are considered to be on a linearScale. The
085: * linearToLinear table is used when the values are considered to
086: * be on the sRGB scale to begin with.
087: */
088: private static final int[] sRGBToLsRGBLut = new int[256];
089: static {
090: final double scale = 1.0 / 255;
091:
092: // System.out.print("S2L: ");
093: for (int i = 0; i < 256; i++) {
094: double value = sRGBToLsRGB(i * scale);
095: sRGBToLsRGBLut[i] = (int) Math.round(value * 255.0);
096: // System.out.print(sRGBToLsRGBLut[i] + ",");
097: }
098: // System.out.println("");
099: }
100:
101: public WritableRaster copyData(WritableRaster wr) {
102: // Get my source.
103: CachableRed src = (CachableRed) getSources().get(0);
104: ColorModel srcCM = src.getColorModel();
105: SampleModel srcSM = src.getSampleModel();
106:
107: // Fast case, SRGB source, INT Pack writable raster...
108: if (srcIssRGB
109: && Any2sRGBRed.is_INT_PACK_COMP(wr.getSampleModel())) {
110: src.copyData(wr);
111: if (srcCM.hasAlpha())
112: GraphicsUtil.coerceData(wr, srcCM, false);
113: Any2sRGBRed.applyLut_INT(wr, sRGBToLsRGBLut);
114: return wr;
115: }
116:
117: if (srcCM == null) {
118: // We don't really know much about this source, let's
119: // guess based on the number of bands...
120:
121: float[][] matrix = null;
122: switch (srcSM.getNumBands()) {
123: case 1:
124: matrix = new float[1][3];
125: matrix[0][0] = 1; // Red
126: matrix[0][1] = 1; // Grn
127: matrix[0][2] = 1; // Blu
128: break;
129: case 2:
130: matrix = new float[2][4];
131: matrix[0][0] = 1; // Red
132: matrix[0][1] = 1; // Grn
133: matrix[0][2] = 1; // Blu
134: matrix[1][3] = 1; // Alpha
135: break;
136: case 3:
137: matrix = new float[3][3];
138: matrix[0][0] = 1; // Red
139: matrix[1][1] = 1; // Grn
140: matrix[2][2] = 1; // Blu
141: break;
142: default:
143: matrix = new float[srcSM.getNumBands()][4];
144: matrix[0][0] = 1; // Red
145: matrix[1][1] = 1; // Grn
146: matrix[2][2] = 1; // Blu
147: matrix[3][3] = 1; // Alpha
148: break;
149: }
150:
151: Raster srcRas = src.getData(wr.getBounds());
152: BandCombineOp op = new BandCombineOp(matrix, null);
153: op.filter(srcRas, wr);
154: } else {
155: ColorModel dstCM = getColorModel();
156: BufferedImage dstBI;
157:
158: if (!dstCM.hasAlpha()) {
159: // No alpha ao we don't have to work around the bug
160: // in the color convert op.
161: dstBI = new BufferedImage(dstCM, wr
162: .createWritableTranslatedChild(0, 0), dstCM
163: .isAlphaPremultiplied(), null);
164: } else {
165: // All this nonsense is to work around the fact that
166: // the Color convert op doesn't properly copy the
167: // Alpha from src to dst.
168: SinglePixelPackedSampleModel dstSM;
169: dstSM = (SinglePixelPackedSampleModel) wr
170: .getSampleModel();
171: int[] masks = dstSM.getBitMasks();
172: SampleModel dstSMNoA = new SinglePixelPackedSampleModel(
173: dstSM.getDataType(), dstSM.getWidth(), dstSM
174: .getHeight(),
175: dstSM.getScanlineStride(), new int[] {
176: masks[0], masks[1], masks[2] });
177: ColorModel dstCMNoA = GraphicsUtil.Linear_sRGB;
178:
179: WritableRaster dstWr;
180: dstWr = Raster.createWritableRaster(dstSMNoA, wr
181: .getDataBuffer(), new Point(0, 0));
182: dstWr = dstWr.createWritableChild(wr.getMinX()
183: - wr.getSampleModelTranslateX(), wr.getMinY()
184: - wr.getSampleModelTranslateY(), wr.getWidth(),
185: wr.getHeight(), 0, 0, null);
186:
187: dstBI = new BufferedImage(dstCMNoA, dstWr, false, null);
188: }
189:
190: // Divide out alpha if we have it. We need to do this since
191: // the color convert may not be a linear operation which may
192: // lead to out of range values.
193: ColorModel srcBICM = srcCM;
194: WritableRaster srcWr;
195: if (srcCM.hasAlpha() && srcCM.isAlphaPremultiplied()) {
196: Rectangle wrR = wr.getBounds();
197: SampleModel sm = srcCM.createCompatibleSampleModel(
198: wrR.width, wrR.height);
199:
200: srcWr = Raster.createWritableRaster(sm, new Point(
201: wrR.x, wrR.y));
202: src.copyData(srcWr);
203: srcBICM = GraphicsUtil.coerceData(srcWr, srcCM, false);
204: } else {
205: Raster srcRas = src.getData(wr.getBounds());
206: srcWr = GraphicsUtil.makeRasterWritable(srcRas);
207: }
208:
209: BufferedImage srcBI;
210: srcBI = new BufferedImage(srcBICM, srcWr
211: .createWritableTranslatedChild(0, 0), false, null);
212:
213: /*
214: * System.out.println("src: " + srcBI.getWidth() + "x" +
215: * srcBI.getHeight());
216: * System.out.println("dst: " + dstBI.getWidth() + "x" +
217: * dstBI.getHeight());
218: */
219:
220: ColorConvertOp op = new ColorConvertOp(null);
221: op.filter(srcBI, dstBI);
222:
223: if (dstCM.hasAlpha())
224: copyBand(srcWr, srcSM.getNumBands() - 1, wr,
225: getSampleModel().getNumBands() - 1);
226: }
227: return wr;
228: }
229:
230: /**
231: * This function 'fixes' the source's color model. Right now
232: * it just selects if it should have one or two bands based on
233: * if the source had an alpha channel.
234: */
235: protected static ColorModel fixColorModel(CachableRed src) {
236: ColorModel cm = src.getColorModel();
237: if (cm != null) {
238: if (cm.hasAlpha())
239: return GraphicsUtil.Linear_sRGB_Unpre;
240:
241: return GraphicsUtil.Linear_sRGB;
242: } else {
243: // No ColorModel so try to make some intelligent
244: // decisions based just on the number of bands...
245: // 1 bands -> replicated into RGB
246: // 2 bands -> Band 0 replicated into RGB & Band 1 -> alpha premult
247: // 3 bands -> sRGB (not-linear?)
248: // 4 bands -> sRGB premult (not-linear?)
249: SampleModel sm = src.getSampleModel();
250:
251: switch (sm.getNumBands()) {
252: case 1:
253: return GraphicsUtil.Linear_sRGB;
254: case 2:
255: return GraphicsUtil.Linear_sRGB_Unpre;
256: case 3:
257: return GraphicsUtil.Linear_sRGB;
258: }
259: return GraphicsUtil.Linear_sRGB_Unpre;
260: }
261: }
262:
263: /**
264: * This function 'fixes' the source's sample model.
265: * Right now it just selects if it should have 3 or 4 bands
266: * based on if the source had an alpha channel.
267: */
268: protected static SampleModel fixSampleModel(CachableRed src) {
269: SampleModel sm = src.getSampleModel();
270: ColorModel cm = src.getColorModel();
271:
272: boolean alpha = false;
273:
274: if (cm != null)
275: alpha = cm.hasAlpha();
276: else {
277: switch (sm.getNumBands()) {
278: case 1:
279: case 3:
280: alpha = false;
281: break;
282: default:
283: alpha = true;
284: break;
285: }
286: }
287: if (alpha)
288: return new SinglePixelPackedSampleModel(
289: DataBuffer.TYPE_INT, sm.getWidth(), sm.getHeight(),
290: new int[] { 0xFF0000, 0xFF00, 0xFF, 0xFF000000 });
291: else
292: return new SinglePixelPackedSampleModel(
293: DataBuffer.TYPE_INT, sm.getWidth(), sm.getHeight(),
294: new int[] { 0xFF0000, 0xFF00, 0xFF });
295: }
296: }
|