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.renderable;
020:
021: import java.awt.RenderingHints;
022: import java.awt.Shape;
023: import java.awt.geom.AffineTransform;
024: import java.awt.geom.Rectangle2D;
025: import java.awt.image.RenderedImage;
026: import java.awt.image.renderable.RenderContext;
027: import java.util.List;
028:
029: import org.apache.batik.ext.awt.image.ARGBChannel;
030: import org.apache.batik.ext.awt.image.GraphicsUtil;
031: import org.apache.batik.ext.awt.image.rendered.AffineRed;
032: import org.apache.batik.ext.awt.image.rendered.CachableRed;
033: import org.apache.batik.ext.awt.image.rendered.DisplacementMapRed;
034:
035: /**
036: * Implements a DisplacementMap operation, which takes pixel values from
037: * another image to spatially displace the input image
038: *
039: * @author <a href="mailto:sheng.pei@eng.sun.com">Sheng Pei</a>
040: * @version $Id: DisplacementMapRable8Bit.java 479564 2006-11-27 09:56:57Z dvholten $
041: */
042: public class DisplacementMapRable8Bit extends
043: AbstractColorInterpolationRable implements DisplacementMapRable {
044:
045: /**
046: * Displacement scale factor
047: */
048: private double scale;
049:
050: /**
051: * Defines which channel in the second source is used
052: * to displace along the x axis
053: */
054: private ARGBChannel xChannelSelector;
055:
056: /**
057: * Defines which channel in the second source is used
058: * to displace along the y axis.
059: */
060: private ARGBChannel yChannelSelector;
061:
062: public DisplacementMapRable8Bit(List sources, double scale,
063: ARGBChannel xChannelSelector, ARGBChannel yChannelSelector) {
064: setSources(sources);
065: setScale(scale);
066: setXChannelSelector(xChannelSelector);
067: setYChannelSelector(yChannelSelector);
068: }
069:
070: public Rectangle2D getBounds2D() {
071: return ((Filter) (getSources().get(0))).getBounds2D();
072: }
073:
074: /**
075: * The displacement scale factor
076: * @param scale can be any number.
077: */
078: public void setScale(double scale) {
079: touch();
080: this .scale = scale;
081: }
082:
083: /**
084: * Returns the displacement scale factor
085: */
086: public double getScale() {
087: return scale;
088: }
089:
090: /**
091: * Sets this filter sources.
092: */
093: public void setSources(List sources) {
094: if (sources.size() != 2) {
095: throw new IllegalArgumentException();
096: }
097: init(sources, null);
098: }
099:
100: /**
101: * Select which component values will be used
102: * for displacement along the X axis
103: * @param xChannelSelector value is among R,
104: * G, B and A.
105: */
106: public void setXChannelSelector(ARGBChannel xChannelSelector) {
107: if (xChannelSelector == null) {
108: throw new IllegalArgumentException();
109: }
110: touch();
111: this .xChannelSelector = xChannelSelector;
112: }
113:
114: /**
115: * Returns the xChannelSelector
116: */
117: public ARGBChannel getXChannelSelector() {
118: return xChannelSelector;
119: }
120:
121: /**
122: * Select which component values will be used
123: * for displacement along the Y axis
124: * @param yChannelSelector value is among R,
125: * G, B and A.
126: */
127: public void setYChannelSelector(ARGBChannel yChannelSelector) {
128: if (yChannelSelector == null) {
129: throw new IllegalArgumentException();
130: }
131: touch();
132: this .yChannelSelector = yChannelSelector;
133: }
134:
135: /**
136: * Returns the yChannelSelector
137: */
138: public ARGBChannel getYChannelSelector() {
139: return yChannelSelector;
140: }
141:
142: public RenderedImage createRendering(RenderContext rc) {
143: // The source image to be displaced.
144: Filter displaced = (Filter) getSources().get(0);
145: // The map giving the displacement.
146: Filter map = (Filter) getSources().get(1);
147:
148: RenderingHints rh = rc.getRenderingHints();
149: if (rh == null)
150: rh = new RenderingHints(null);
151:
152: // update the current affine transform
153: AffineTransform at = rc.getTransform();
154:
155: // This splits out the scale from the rest of
156: // the transformation.
157: double sx = at.getScaleX();
158: double sy = at.getScaleY();
159:
160: double shx = at.getShearX();
161: double shy = at.getShearY();
162:
163: double tx = at.getTranslateX();
164: double ty = at.getTranslateY();
165:
166: // The Scale is the "hypotonose" of the matrix vectors.
167: double atScaleX = Math.sqrt(sx * sx + shy * shy);
168: double atScaleY = Math.sqrt(sy * sy + shx * shx);
169:
170: // Now, apply the filter
171: //
172: float scaleX = (float) (scale * atScaleX);
173: float scaleY = (float) (scale * atScaleY);
174:
175: // If both scale factors are zero then we don't
176: // affect the source image so just return it...
177: if ((scaleX == 0) && (scaleY == 0))
178: return displaced.createRendering(rc);
179:
180: // if ((scaleX > 255) || (scaleY > 255)) {
181: // System.out.println("Scales: [" + scaleX + ", " + scaleY + "]");
182: // }
183:
184: AffineTransform srcAt = AffineTransform.getScaleInstance(
185: atScaleX, atScaleY);
186:
187: Shape origAOI = rc.getAreaOfInterest();
188: if (origAOI == null)
189: origAOI = getBounds2D();
190:
191: Rectangle2D aoiR = origAOI.getBounds2D();
192:
193: RenderContext srcRc = new RenderContext(srcAt, aoiR, rh);
194: RenderedImage mapRed = map.createRendering(srcRc);
195:
196: if (mapRed == null)
197: return null;
198:
199: // Grow the area of interest in user space. to account for
200: // the max surround needs of displacement map.
201: aoiR = new Rectangle2D.Double(aoiR.getX() - scale / 2, aoiR
202: .getY()
203: - scale / 2, aoiR.getWidth() + scale, aoiR.getHeight()
204: + scale);
205:
206: Rectangle2D displacedRect = displaced.getBounds2D();
207: if (!aoiR.intersects(displacedRect))
208: return null;
209:
210: aoiR = aoiR.createIntersection(displacedRect);
211: srcRc = new RenderContext(srcAt, aoiR, rh);
212: RenderedImage displacedRed = displaced.createRendering(srcRc);
213:
214: if (displacedRed == null)
215: return null;
216:
217: mapRed = convertSourceCS(mapRed);
218:
219: //
220: // Build a Displacement Map Red from the two sources
221: //
222:
223: CachableRed cr = new DisplacementMapRed(GraphicsUtil
224: .wrap(displacedRed), GraphicsUtil.wrap(mapRed),
225: xChannelSelector, yChannelSelector, scaleX, scaleY, rh);
226: //
227: // Apply the non scaling part of the transform now,
228: // if different from identity.
229: //
230: AffineTransform resAt = new AffineTransform(sx / atScaleX, shy
231: / atScaleX, shx / atScaleY, sy / atScaleY, tx, ty);
232:
233: if (!resAt.isIdentity())
234: cr = new AffineRed(cr, resAt, rh);
235:
236: return cr;
237: }
238:
239: /**
240: * Returns the region of input data is is required to generate
241: * outputRgn.
242: * @param srcIndex The source to do the dependency calculation for.
243: * @param outputRgn The region of output you are interested in
244: * generating dependencies for. The is given in the user coordiate
245: * system for this node.
246: * @return The region of input required. This is in the user
247: * coordinate system for the source indicated by srcIndex.
248: */
249: public Shape getDependencyRegion(int srcIndex, Rectangle2D outputRgn) {
250: // NOTE: This needs to grow the region!!!
251: // Morphology actually needs a larger area of input than
252: // it outputs.
253: return super .getDependencyRegion(srcIndex, outputRgn);
254: }
255:
256: /**
257: * This calculates the region of output that is affected by a change
258: * in a region of input.
259: * @param srcIndex The input that inputRgn reflects changes in.
260: * @param inputRgn the region of input that has changed, used to
261: * calculate the returned shape. This is given in the user
262: * coordinate system of the source indicated by srcIndex.
263: * @return The region of output that would be invalid given
264: * a change to inputRgn of the source selected by srcIndex.
265: * this is in the user coordinate system of this node.
266: */
267: public Shape getDirtyRegion(int srcIndex, Rectangle2D inputRgn) {
268: // NOTE: This needs to grow the region!!!
269: // Changes in the input region affect a larger area of
270: // output than the input.
271: return super.getDirtyRegion(srcIndex, inputRgn);
272: }
273:
274: }
|