/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.ext.awt.image.renderable;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderContext;
import org.apache.batik.ext.awt.image.PadMode;
import org.apache.batik.ext.awt.image.rendered.AffineRed;
import org.apache.batik.ext.awt.image.rendered.CachableRed;
import org.apache.batik.ext.awt.image.rendered.GaussianBlurRed8Bit;
import org.apache.batik.ext.awt.image.rendered.PadRed;
/**
* GaussianBlurRable implementation
*
* @author <a href="mailto:vincent.hardy@eng.sun.com>Vincent Hardy</a>
* @version $Id: GaussianBlurRable8Bit.java,v 1.7 2003/04/11 13:57:48 vhardy Exp $
*/
public class GaussianBlurRable8Bit
extends AbstractColorInterpolationRable
implements GaussianBlurRable {
/**
* Deviation along the x-axis
*/
private double stdDeviationX;
/**
* Deviation along the y-axis
*/
private double stdDeviationY;
public GaussianBlurRable8Bit(Filter src,
double stdevX, double stdevY) {
super(src, null);
setStdDeviationX(stdevX);
setStdDeviationY(stdevY);
}
/**
* The deviation along the x axis, in user space.
* @param stdDeviationX should be greater than zero.
*/
public void setStdDeviationX(double stdDeviationX){
if(stdDeviationX < 0){
throw new IllegalArgumentException();
}
touch();
this.stdDeviationX = stdDeviationX;
}
/**
* The deviation along the y axis, in user space.
* @param stdDeviationY should be greater than zero
*/
public void setStdDeviationY(double stdDeviationY){
if(stdDeviationY < 0){
throw new IllegalArgumentException();
}
touch();
this.stdDeviationY = stdDeviationY;
}
/**
* Returns the deviation along the x-axis, in user space.
*/
public double getStdDeviationX(){
return stdDeviationX;
}
/**
* Returns the deviation along the y-axis, in user space.
*/
public double getStdDeviationY(){
return stdDeviationY;
}
/**
* Sets the source of the blur operation
*/
public void setSource(Filter src){
init(src, null);
}
/**
* Constant: 3*sqrt(2*PI)/4
*/
static final float DSQRT2PI = (float)(Math.sqrt(2*Math.PI)*3.0/4.0);
/**
* Grow the source's bounds
*/
public Rectangle2D getBounds2D(){
Rectangle2D src = getSource().getBounds2D();
float dX = (float)(stdDeviationX*DSQRT2PI);
float dY = (float)(stdDeviationY*DSQRT2PI);
float radX = 3*dX/2;
float radY = 3*dY/2;
return new Rectangle2D.Float
((float)(src.getMinX() -radX),
(float)(src.getMinY() -radY),
(float)(src.getWidth() +2*radX),
(float)(src.getHeight()+2*radY));
}
/**
* Returns the source of the blur operation
*/
public Filter getSource(){
return (Filter)getSources().get(0);
}
public final static double eps = 0.0001;
public static boolean eps_eq(double f1, double f2) {
return ((f1 >= f2-eps) && (f1 <= f2+eps));
}
public static boolean eps_abs_eq(double f1, double f2) {
if (f1 <0) f1 = -f1;
if (f2 <0) f2 = -f2;
return eps_eq(f1, f2);
}
public RenderedImage createRendering(RenderContext rc) {
// Just copy over the rendering hints.
RenderingHints rh = rc.getRenderingHints();
if (rh == null) rh = new RenderingHints(null);
// update the current affine transform
AffineTransform at = rc.getTransform();
// This splits out the scale and applies it
// prior to the Gaussian. Then after appying the gaussian
// it applies the shear (rotation) and translation components.
double sx = at.getScaleX();
double sy = at.getScaleY();
double shx = at.getShearX();
double shy = at.getShearY();
double tx = at.getTranslateX();
double ty = at.getTranslateY();
// The Scale is the "hypotonose" of the matrix vectors.
double scaleX = Math.sqrt(sx*sx + shy*shy);
double scaleY = Math.sqrt(sy*sy + shx*shx);
double sdx = stdDeviationX*scaleX;
double sdy = stdDeviationY*scaleY;
// This is the affine transform between our usr space and an
// intermediate space which is scaled similarly to our device
// space but is still axially aligned with our device space.
AffineTransform srcAt;
// This is the affine transform between our intermediate
// coordinate space and the real device space, or null (if
// we don't need an intermediate space).
AffineTransform resAt;
int outsetX, outsetY;
if ((sdx < 10) &&
(sdy < 10) &&
eps_eq (sdx, sdy) &&
eps_abs_eq(sx/scaleX, sy/scaleY)) {
// Ok we have a square Gaussian kernel which means it is
// circularly symetric, further our residual matrix (after
// removing scaling) is a rotation matrix (perhaps with
// mirroring), thus we can generate our source directly in
// device space and convolve there rather than going to an
// intermediate space (axially aligned with usr space) and
// then completing the requested rotation/shear, with an
// AffineRed...
srcAt = at;
resAt = null;
outsetX = 0;
outsetY = 0;
} else {
// Limit std dev to 10. Put any extra into our
// residual matrix. This will effectively linearly
// interpolate, but with such a large StdDev the
// function is fairly smooth anyway...
if (sdx > 10) {
scaleX = scaleX*10/sdx;
sdx = 10;
}
if (sdy > 10) {
scaleY = scaleY*10/sdy;
sdy = 10;
}
// Scale to device coords.
srcAt = AffineTransform.getScaleInstance(scaleX, scaleY);
// The shear/rotation simply divides out the
// common scale factor in the matrix.
resAt = new AffineTransform(sx/scaleX, shy/scaleX,
shx/scaleY, sy/scaleY,
tx, ty);
// Add a pixel all around for the affine to interpolate with.
outsetX = 1;
outsetY = 1;
}
Shape aoi = rc.getAreaOfInterest();
if(aoi == null)
aoi = getBounds2D();
Shape devShape = srcAt.createTransformedShape(aoi);
Rectangle devRect = devShape.getBounds();
outsetX += GaussianBlurRed8Bit.surroundPixels(sdx, rh);
outsetY += GaussianBlurRed8Bit.surroundPixels(sdy, rh);
devRect.x -= outsetX;
devRect.y -= outsetY;
devRect.width += 2*outsetX;
devRect.height += 2*outsetY;
Rectangle2D r;
try {
AffineTransform invSrcAt = srcAt.createInverse();
r = invSrcAt.createTransformedShape(devRect).getBounds2D();
} catch (NoninvertibleTransformException nte) {
// Grow the region in usr space.
r = aoi.getBounds2D();
r = new Rectangle2D.Double(r.getX()-outsetX/scaleX,
r.getY()-outsetY/scaleY,
r.getWidth() +2*outsetX/scaleX,
r.getHeight()+2*outsetY/scaleY);
}
RenderedImage ri;
ri = getSource().createRendering(new RenderContext(srcAt, r, rh));
if (ri == null)
return null;
CachableRed cr = convertSourceCS(ri);
// System.out.println("DevRect: " + devRect);
if (!devRect.equals(cr.getBounds())) {
// System.out.println("MisMatch Dev:" + devRect);
// System.out.println(" CR :" + cr.getBounds());
cr = new PadRed(cr, devRect, PadMode.ZERO_PAD, rh);
}
cr = new GaussianBlurRed8Bit(cr, sdx, sdy, rh);
if ((resAt != null) && (!resAt.isIdentity()))
cr = new AffineRed(cr, resAt, rh);
return cr;
}
/**
* Returns the region of input data is is required to generate
* outputRgn.
* @param srcIndex The source to do the dependency calculation for.
* @param outputRgn The region of output you are interested in
* generating dependencies for. The is given in the user coordiate
* system for this node.
* @return The region of input required. This is in the user
* coordinate system for the source indicated by srcIndex.
*/
public Shape getDependencyRegion(int srcIndex, Rectangle2D outputRgn){
if(srcIndex != 0)
outputRgn = null;
else {
// There is only one source in GaussianBlur
float dX = (float)(stdDeviationX*DSQRT2PI);
float dY = (float)(stdDeviationY*DSQRT2PI);
float radX = 3*dX/2;
float radY = 3*dY/2;
outputRgn = new Rectangle2D.Float
((float)(outputRgn.getMinX() -radX),
(float)(outputRgn.getMinY() -radY),
(float)(outputRgn.getWidth() +2*radX),
(float)(outputRgn.getHeight()+2*radY));
Rectangle2D bounds = getBounds2D();
if (outputRgn.intersects(bounds) == false)
return new Rectangle2D.Float();
// Intersect with output region
outputRgn = outputRgn.createIntersection(bounds);
}
return outputRgn;
}
/**
* This calculates the region of output that is affected by a change
* in a region of input.
* @param srcIndex The input that inputRgn reflects changes in.
* @param inputRgn the region of input that has changed, used to
* calculate the returned shape. This is given in the user
* coordinate system of the source indicated by srcIndex.
* @return The region of output that would be invalid given
* a change to inputRgn of the source selected by srcIndex.
* this is in the user coordinate system of this node.
*/
public Shape getDirtyRegion(int srcIndex, Rectangle2D inputRgn){
Rectangle2D dirtyRegion = null;
if(srcIndex == 0){
float dX = (float)(stdDeviationX*DSQRT2PI);
float dY = (float)(stdDeviationY*DSQRT2PI);
float radX = 3*dX/2;
float radY = 3*dY/2;
inputRgn = new Rectangle2D.Float
((float)(inputRgn.getMinX() -radX),
(float)(inputRgn.getMinY() -radY),
(float)(inputRgn.getWidth() +2*radX),
(float)(inputRgn.getHeight()+2*radY));
Rectangle2D bounds = getBounds2D();
if (inputRgn.intersects(bounds) == false)
return new Rectangle2D.Float();
// Intersect with input region
dirtyRegion = inputRgn.createIntersection(bounds);
}
return dirtyRegion;
}
}