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.Rectangle;
022: import java.awt.RenderingHints;
023: import java.awt.Shape;
024: import java.awt.geom.AffineTransform;
025: import java.awt.geom.NoninvertibleTransformException;
026: import java.awt.geom.Rectangle2D;
027: import java.awt.image.RenderedImage;
028: import java.awt.image.renderable.RenderContext;
029:
030: import org.apache.batik.ext.awt.image.PadMode;
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.GaussianBlurRed8Bit;
034: import org.apache.batik.ext.awt.image.rendered.PadRed;
035:
036: /**
037: * GaussianBlurRable implementation
038: *
039: * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
040: * @version $Id: GaussianBlurRable8Bit.java 501495 2007-01-30 18:00:36Z dvholten $
041: */
042: public class GaussianBlurRable8Bit extends
043: AbstractColorInterpolationRable implements GaussianBlurRable {
044:
045: /**
046: * Deviation along the x-axis
047: */
048: private double stdDeviationX;
049:
050: /**
051: * Deviation along the y-axis
052: */
053: private double stdDeviationY;
054:
055: public GaussianBlurRable8Bit(Filter src, double stdevX,
056: double stdevY) {
057: super (src, null);
058: setStdDeviationX(stdevX);
059: setStdDeviationY(stdevY);
060: }
061:
062: /**
063: * The deviation along the x axis, in user space.
064: * @param stdDeviationX should be greater than zero.
065: */
066: public void setStdDeviationX(double stdDeviationX) {
067: if (stdDeviationX < 0) {
068: throw new IllegalArgumentException();
069: }
070:
071: touch();
072: this .stdDeviationX = stdDeviationX;
073: }
074:
075: /**
076: * The deviation along the y axis, in user space.
077: * @param stdDeviationY should be greater than zero
078: */
079: public void setStdDeviationY(double stdDeviationY) {
080: if (stdDeviationY < 0) {
081: throw new IllegalArgumentException();
082: }
083: touch();
084: this .stdDeviationY = stdDeviationY;
085: }
086:
087: /**
088: * Returns the deviation along the x-axis, in user space.
089: */
090: public double getStdDeviationX() {
091: return stdDeviationX;
092: }
093:
094: /**
095: * Returns the deviation along the y-axis, in user space.
096: */
097: public double getStdDeviationY() {
098: return stdDeviationY;
099: }
100:
101: /**
102: * Sets the source of the blur operation
103: */
104: public void setSource(Filter src) {
105: init(src, null);
106: }
107:
108: /**
109: * Constant: 3*sqrt(2*PI)/4
110: */
111: static final double DSQRT2PI = (Math.sqrt(2 * Math.PI) * 3.0 / 4.0);
112:
113: /**
114: * Grow the source's bounds
115: */
116: public Rectangle2D getBounds2D() {
117: Rectangle2D src = getSource().getBounds2D();
118: float dX = (float) (stdDeviationX * DSQRT2PI);
119: float dY = (float) (stdDeviationY * DSQRT2PI);
120: float radX = 3 * dX / 2;
121: float radY = 3 * dY / 2;
122: return new Rectangle2D.Float((float) (src.getMinX() - radX),
123: (float) (src.getMinY() - radY),
124: (float) (src.getWidth() + 2 * radX), (float) (src
125: .getHeight() + 2 * radY));
126: }
127:
128: /**
129: * Returns the source of the blur operation
130: */
131: public Filter getSource() {
132: return (Filter) getSources().get(0);
133: }
134:
135: public static final double eps = 0.0001;
136:
137: public static boolean eps_eq(double f1, double f2) {
138: return ((f1 >= f2 - eps) && (f1 <= f2 + eps));
139: }
140:
141: public static boolean eps_abs_eq(double f1, double f2) {
142: if (f1 < 0)
143: f1 = -f1;
144: if (f2 < 0)
145: f2 = -f2;
146: return eps_eq(f1, f2);
147: }
148:
149: public RenderedImage createRendering(RenderContext rc) {
150: // Just copy over the rendering hints.
151: RenderingHints rh = rc.getRenderingHints();
152: if (rh == null)
153: rh = new RenderingHints(null);
154:
155: // update the current affine transform
156: AffineTransform at = rc.getTransform();
157:
158: // This splits out the scale and applies it
159: // prior to the Gaussian. Then after appying the gaussian
160: // it applies the shear (rotation) and translation components.
161: double sx = at.getScaleX();
162: double sy = at.getScaleY();
163:
164: double shx = at.getShearX();
165: double shy = at.getShearY();
166:
167: double tx = at.getTranslateX();
168: double ty = at.getTranslateY();
169:
170: // The Scale is the "hypotonose" of the matrix vectors.
171: double scaleX = Math.sqrt(sx * sx + shy * shy);
172: double scaleY = Math.sqrt(sy * sy + shx * shx);
173:
174: double sdx = stdDeviationX * scaleX;
175: double sdy = stdDeviationY * scaleY;
176:
177: // This is the affine transform between our usr space and an
178: // intermediate space which is scaled similarly to our device
179: // space but is still axially aligned with our device space.
180: AffineTransform srcAt;
181:
182: // This is the affine transform between our intermediate
183: // coordinate space and the real device space, or null (if
184: // we don't need an intermediate space).
185: AffineTransform resAt;
186:
187: int outsetX, outsetY;
188: if ((sdx < 10) && (sdy < 10) && eps_eq(sdx, sdy)
189: && eps_abs_eq(sx / scaleX, sy / scaleY)) {
190: // Ok we have a square Gaussian kernel which means it is
191: // circularly symetric, further our residual matrix (after
192: // removing scaling) is a rotation matrix (perhaps with
193: // mirroring), thus we can generate our source directly in
194: // device space and convolve there rather than going to an
195: // intermediate space (axially aligned with usr space) and
196: // then completing the requested rotation/shear, with an
197: // AffineRed...
198:
199: srcAt = at;
200: resAt = null;
201: outsetX = 0;
202: outsetY = 0;
203: } else {
204:
205: // Limit std dev to 10. Put any extra into our
206: // residual matrix. This will effectively linearly
207: // interpolate, but with such a large StdDev the
208: // function is fairly smooth anyway...
209: if (sdx > 10) {
210: scaleX = scaleX * 10 / sdx;
211: sdx = 10;
212: }
213: if (sdy > 10) {
214: scaleY = scaleY * 10 / sdy;
215: sdy = 10;
216: }
217:
218: // Scale to device coords.
219: srcAt = AffineTransform.getScaleInstance(scaleX, scaleY);
220:
221: // The shear/rotation simply divides out the
222: // common scale factor in the matrix.
223: resAt = new AffineTransform(sx / scaleX, shy / scaleX, shx
224: / scaleY, sy / scaleY, tx, ty);
225: // Add a pixel all around for the affine to interpolate with.
226: outsetX = 1;
227: outsetY = 1;
228: }
229:
230: Shape aoi = rc.getAreaOfInterest();
231: if (aoi == null)
232: aoi = getBounds2D();
233:
234: Shape devShape = srcAt.createTransformedShape(aoi);
235: Rectangle devRect = devShape.getBounds();
236:
237: outsetX += GaussianBlurRed8Bit.surroundPixels(sdx, rh);
238: outsetY += GaussianBlurRed8Bit.surroundPixels(sdy, rh);
239:
240: devRect.x -= outsetX;
241: devRect.y -= outsetY;
242: devRect.width += 2 * outsetX;
243: devRect.height += 2 * outsetY;
244:
245: Rectangle2D r;
246: try {
247: AffineTransform invSrcAt = srcAt.createInverse();
248: r = invSrcAt.createTransformedShape(devRect).getBounds2D();
249: } catch (NoninvertibleTransformException nte) {
250: // Grow the region in usr space.
251: r = aoi.getBounds2D();
252: r = new Rectangle2D.Double(r.getX() - outsetX / scaleX, r
253: .getY()
254: - outsetY / scaleY, r.getWidth() + 2 * outsetX
255: / scaleX, r.getHeight() + 2 * outsetY / scaleY);
256: }
257:
258: RenderedImage ri;
259: ri = getSource().createRendering(
260: new RenderContext(srcAt, r, rh));
261: if (ri == null)
262: return null;
263:
264: CachableRed cr = convertSourceCS(ri);
265:
266: // System.out.println("DevRect: " + devRect);
267:
268: if (!devRect.equals(cr.getBounds())) {
269: // System.out.println("MisMatch Dev:" + devRect);
270: // System.out.println(" CR :" + cr.getBounds());
271: cr = new PadRed(cr, devRect, PadMode.ZERO_PAD, rh);
272: }
273:
274: cr = new GaussianBlurRed8Bit(cr, sdx, sdy, rh);
275:
276: if ((resAt != null) && (!resAt.isIdentity()))
277: cr = new AffineRed(cr, resAt, rh);
278:
279: return cr;
280: }
281:
282: /**
283: * Returns the region of input data is is required to generate
284: * outputRgn.
285: * @param srcIndex The source to do the dependency calculation for.
286: * @param outputRgn The region of output you are interested in
287: * generating dependencies for. The is given in the user coordiate
288: * system for this node.
289: * @return The region of input required. This is in the user
290: * coordinate system for the source indicated by srcIndex.
291: */
292: public Shape getDependencyRegion(int srcIndex, Rectangle2D outputRgn) {
293: if (srcIndex != 0)
294: outputRgn = null;
295: else {
296: // There is only one source in GaussianBlur
297: float dX = (float) (stdDeviationX * DSQRT2PI);
298: float dY = (float) (stdDeviationY * DSQRT2PI);
299: float radX = 3 * dX / 2;
300: float radY = 3 * dY / 2;
301: outputRgn = new Rectangle2D.Float((float) (outputRgn
302: .getMinX() - radX),
303: (float) (outputRgn.getMinY() - radY),
304: (float) (outputRgn.getWidth() + 2 * radX),
305: (float) (outputRgn.getHeight() + 2 * radY));
306:
307: Rectangle2D bounds = getBounds2D();
308: if (!outputRgn.intersects(bounds))
309: return new Rectangle2D.Float();
310: // Intersect with output region
311: outputRgn = outputRgn.createIntersection(bounds);
312: }
313:
314: return outputRgn;
315: }
316:
317: /**
318: * This calculates the region of output that is affected by a change
319: * in a region of input.
320: * @param srcIndex The input that inputRgn reflects changes in.
321: * @param inputRgn the region of input that has changed, used to
322: * calculate the returned shape. This is given in the user
323: * coordinate system of the source indicated by srcIndex.
324: * @return The region of output that would be invalid given
325: * a change to inputRgn of the source selected by srcIndex.
326: * this is in the user coordinate system of this node.
327: */
328: public Shape getDirtyRegion(int srcIndex, Rectangle2D inputRgn) {
329: Rectangle2D dirtyRegion = null;
330: if (srcIndex == 0) {
331: float dX = (float) (stdDeviationX * DSQRT2PI);
332: float dY = (float) (stdDeviationY * DSQRT2PI);
333: float radX = 3 * dX / 2;
334: float radY = 3 * dY / 2;
335: inputRgn = new Rectangle2D.Float((float) (inputRgn
336: .getMinX() - radX),
337: (float) (inputRgn.getMinY() - radY),
338: (float) (inputRgn.getWidth() + 2 * radX),
339: (float) (inputRgn.getHeight() + 2 * radY));
340:
341: Rectangle2D bounds = getBounds2D();
342: if (!inputRgn.intersects(bounds))
343: return new Rectangle2D.Float();
344: // Intersect with input region
345: dirtyRegion = inputRgn.createIntersection(bounds);
346: }
347:
348: return dirtyRegion;
349: }
350:
351: }
|