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.Point;
022: import java.awt.Rectangle;
023: import java.awt.RenderingHints;
024: import java.awt.Transparency;
025: import java.awt.color.ColorSpace;
026: import java.awt.geom.AffineTransform;
027: import java.awt.geom.NoninvertibleTransformException;
028: import java.awt.geom.Point2D;
029: import java.awt.image.AffineTransformOp;
030: import java.awt.image.BufferedImage;
031: import java.awt.image.ColorModel;
032: import java.awt.image.ComponentColorModel;
033: import java.awt.image.DataBuffer;
034: import java.awt.image.DirectColorModel;
035: import java.awt.image.Raster;
036: import java.awt.image.SampleModel;
037: import java.awt.image.WritableRaster;
038:
039: import org.apache.batik.ext.awt.image.GraphicsUtil;
040:
041: /**
042: * This is an implementation of an affine operation as a RenderedImage.
043: * Right now the implementation makes use of the AffineBufferedImageOp
044: * to do the work. Eventually this may move to be more tiled in nature.
045: *
046: * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
047: * @version $Id: AffineRed.java 478276 2006-11-22 18:33:37Z dvholten $ */
048: public class AffineRed extends AbstractRed {
049:
050: RenderingHints hints;
051: AffineTransform src2me;
052: AffineTransform me2src;
053:
054: public AffineTransform getTransform() {
055: return (AffineTransform) src2me.clone();
056: }
057:
058: public CachableRed getSource() {
059: return (CachableRed) getSources().get(0);
060: }
061:
062: public AffineRed(CachableRed src, AffineTransform src2me,
063: RenderingHints hints) {
064: super (); // We _must_ call init...
065:
066: this .src2me = src2me;
067: this .hints = hints;
068:
069: try {
070: me2src = src2me.createInverse();
071: } catch (NoninvertibleTransformException nite) {
072: me2src = null;
073: }
074:
075: // Calculate my bounds by applying the affine transform to
076: // my input data..codec/
077: Rectangle srcBounds = src.getBounds();
078: // srcBounds.grow(-1,-1);
079: Rectangle myBounds;
080: myBounds = src2me.createTransformedShape(srcBounds).getBounds();
081:
082: // If the output buffer is not premultiplied in certain cases it
083: // fails to properly divide out the Alpha (it always does
084: // the affine on premultiplied data), hence you get ugly
085: // back aliasing effects...
086: ColorModel cm = fixColorModel(src);
087:
088: // fix my sample model so it makes sense given my size.
089: SampleModel sm = fixSampleModel(src, cm, myBounds);
090:
091: Point2D pt = new Point2D.Float(src.getTileGridXOffset(), src
092: .getTileGridYOffset());
093: pt = src2me.transform(pt, null);
094:
095: // Finish initializing our base class...
096: init(src, myBounds, cm, sm, (int) pt.getX(), (int) pt.getY(),
097: null);
098: }
099:
100: public WritableRaster copyData(WritableRaster wr) {
101:
102: // System.out.println("Affine CopyData:" + wr);
103:
104: // copyToRaster(wr);
105: PadRed.ZeroRecter zr = PadRed.ZeroRecter.getZeroRecter(wr);
106: zr.zeroRect(new Rectangle(wr.getMinX(), wr.getMinY(), wr
107: .getWidth(), wr.getHeight()));
108: genRect(wr);
109: return wr;
110: }
111:
112: public Raster getTile(int x, int y) {
113: if (me2src == null)
114: return null;
115:
116: int tx = tileGridXOff + x * tileWidth;
117: int ty = tileGridYOff + y * tileHeight;
118: Point pt = new Point(tx, ty);
119: WritableRaster wr = Raster.createWritableRaster(sm, pt);
120: genRect(wr);
121:
122: return wr;
123: }
124:
125: public void genRect(WritableRaster wr) {
126: if (me2src == null)
127: return;
128:
129: Rectangle srcR = me2src.createTransformedShape(wr.getBounds())
130: .getBounds();
131:
132: // System.out.println("Affine wrR: " + wr.getBounds());
133: // System.out.println("Affine srcR: " + srcR);
134:
135: // Outset by two pixels so we get context for interpolation...
136: srcR.setBounds(srcR.x - 1, srcR.y - 1, srcR.width + 2,
137: srcR.height + 2);
138:
139: // Don't try and get data from src that it doesn't have...
140: CachableRed src = (CachableRed) getSources().get(0);
141:
142: // Raster srcRas = src.getData(srcR);
143:
144: if (!srcR.intersects(src.getBounds()))
145: return;
146: Raster srcRas = src.getData(srcR.intersection(src.getBounds()));
147:
148: if (srcRas == null)
149: return;
150:
151: // This works around the problem that the buffered ops
152: // completely ignore the coords of the Rasters passed in.
153: AffineTransform aff = (AffineTransform) src2me.clone();
154:
155: // Translate what is at 0,0 (which will be what our current
156: // minX/Y is) to our current minX,minY.
157: aff.concatenate(AffineTransform.getTranslateInstance(srcRas
158: .getMinX(), srcRas.getMinY()));
159:
160: Point2D srcPt = new Point2D.Float(wr.getMinX(), wr.getMinY());
161: srcPt = me2src.transform(srcPt, null);
162:
163: Point2D destPt = new Point2D.Double(srcPt.getX()
164: - srcRas.getMinX(), srcPt.getY() - srcRas.getMinY());
165:
166: destPt = aff.transform(destPt, null);
167:
168: // Translate what will be at minX,minY to zero, zero
169: // which where java2d will think the real minX,minY is.
170: aff.preConcatenate(AffineTransform.getTranslateInstance(-destPt
171: .getX(), -destPt.getY()));
172:
173: AffineTransformOp op = new AffineTransformOp(aff, hints);
174:
175: BufferedImage srcBI, myBI;
176: ColorModel srcCM = src.getColorModel();
177: ColorModel myCM = getColorModel();
178:
179: WritableRaster srcWR = (WritableRaster) srcRas;
180: // If the output buffer is not premultiplied in certain cases
181: // it fails to properly divide out the Alpha (it always does
182: // the affine on premultiplied data). We help it out by
183: // premultiplying for it.
184: srcCM = GraphicsUtil.coerceData(srcWR, srcCM, true);
185: srcBI = new BufferedImage(srcCM, srcWR
186: .createWritableTranslatedChild(0, 0), srcCM
187: .isAlphaPremultiplied(), null);
188:
189: myBI = new BufferedImage(myCM, wr
190: .createWritableTranslatedChild(0, 0), myCM
191: .isAlphaPremultiplied(), null);
192:
193: op.filter(srcBI, myBI);
194:
195: // if ((count % 40) == 0) {
196: // org.apache.batik.ImageDisplay.showImage("Src: " , srcBI);
197: // org.apache.batik.ImageDisplay.showImage("Dst: " , myBI);
198: // }
199: // count++;
200: }
201:
202: // int count=0;
203:
204: protected static ColorModel fixColorModel(CachableRed src) {
205: ColorModel cm = src.getColorModel();
206:
207: if (cm.hasAlpha()) {
208: if (!cm.isAlphaPremultiplied())
209: cm = GraphicsUtil.coerceColorModel(cm, true);
210: return cm;
211: }
212:
213: ColorSpace cs = cm.getColorSpace();
214:
215: int b = src.getSampleModel().getNumBands() + 1;
216: if (b == 4) {
217: int[] masks = new int[4];
218: for (int i = 0; i < b - 1; i++)
219: masks[i] = 0xFF0000 >> (8 * i);
220: masks[3] = 0xFF << (8 * (b - 1));
221:
222: return new DirectColorModel(cs, 8 * b, masks[0], masks[1],
223: masks[2], masks[3], true, DataBuffer.TYPE_INT);
224: }
225:
226: int[] bits = new int[b];
227: for (int i = 0; i < b; i++)
228: bits[i] = 8;
229: return new ComponentColorModel(cs, bits, true, true,
230: Transparency.TRANSLUCENT, DataBuffer.TYPE_INT);
231:
232: }
233:
234: /**
235: * This function 'fixes' the source's sample model.
236: * right now it just ensures that the sample model isn't
237: * much larger than my width.
238: */
239: protected SampleModel fixSampleModel(CachableRed src,
240: ColorModel cm, Rectangle bounds) {
241: SampleModel sm = src.getSampleModel();
242: int defSz = AbstractTiledRed.getDefaultTileSize();
243:
244: int w = sm.getWidth();
245: if (w < defSz)
246: w = defSz;
247: if (w > bounds.width)
248: w = bounds.width;
249: int h = sm.getHeight();
250: if (h < defSz)
251: h = defSz;
252: if (h > bounds.height)
253: h = bounds.height;
254:
255: if ((w <= 0) || (h <= 0)) {
256: w = 1;
257: h = 1;
258: }
259:
260: return cm.createCompatibleSampleModel(w, h);
261: }
262: }
|