package com.hphoto.image;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
/**
* A filter which draws a drop shadow based on the alpha channel of the image.
*/
public class ShadowFilter extends AbstractBufferedImageOp {
private float radius = 5;
private float angle = (float)Math.PI*6/4;
private float distance = 5;
private float opacity = 0.5f;
private boolean addMargins = false;
private boolean shadowOnly = false;
private int shadowColor = 0xff000000;
/**
* Construct a ShadowFilter.
*/
public ShadowFilter() {
}
/**
* Construct a ShadowFilter.
* @param radius the radius of the shadow
* @param xOffset the X offset of the shadow
* @param yOffset the Y offset of the shadow
* @param opacity the opacity of the shadow
*/
public ShadowFilter(float radius, float xOffset, float yOffset, float opacity) {
this.radius = radius;
this.angle = (float)Math.atan2(yOffset, xOffset);
this.distance = (float)Math.sqrt(xOffset*xOffset + yOffset*yOffset);
this.opacity = opacity;
}
/**
* Specifies the angle of the shadow.
* @param angle the angle of the shadow.
* @angle
* @see #getAngle
*/
public void setAngle(float angle) {
this.angle = angle;
}
/**
* Returns the angle of the shadow.
* @return the angle of the shadow.
* @see #setAngle
*/
public float getAngle() {
return angle;
}
/**
* Set the distance of the shadow.
* @param distance the distance.
* @see #getDistance
*/
public void setDistance(float distance) {
this.distance = distance;
}
/**
* Get the distance of the shadow.
* @return the distance.
* @see #setDistance
*/
public float getDistance() {
return distance;
}
/**
* Set the radius of the kernel, and hence the amount of blur. The bigger the radius, the longer this filter will take.
* @param radius the radius of the blur in pixels.
* @see #getRadius
*/
public void setRadius(float radius) {
this.radius = radius;
}
/**
* Get the radius of the kernel.
* @return the radius
* @see #setRadius
*/
public float getRadius() {
return radius;
}
/**
* Set the opacity of the shadow.
* @param opacity the opacity.
* @see #getOpacity
*/
public void setOpacity(float opacity) {
this.opacity = opacity;
}
/**
* Get the opacity of the shadow.
* @return the opacity.
* @see #setOpacity
*/
public float getOpacity() {
return opacity;
}
/**
* Set the color of the shadow.
* @param shadowColor the color.
* @see #getShadowColor
*/
public void setShadowColor(int shadowColor) {
this.shadowColor = shadowColor;
}
/**
* Get the color of the shadow.
* @return the color.
* @see #setShadowColor
*/
public int getShadowColor() {
return shadowColor;
}
/**
* Set whether to increase the size of the output image to accomodate the shadow.
* @param addMargins true to add margins.
* @see #getAddMargins
*/
public void setAddMargins(boolean addMargins) {
this.addMargins = addMargins;
}
/**
* Get whether to increase the size of the output image to accomodate the shadow.
* @return true to add margins.
* @see #setAddMargins
*/
public boolean getAddMargins() {
return addMargins;
}
/**
* Set whether to only draw the shadow without the original image.
* @param shadowOnly true to only draw the shadow.
* @see #getShadowOnly
*/
public void setShadowOnly(boolean shadowOnly) {
this.shadowOnly = shadowOnly;
}
/**
* Get whether to only draw the shadow without the original image.
* @return true to only draw the shadow.
* @see #setShadowOnly
*/
public boolean getShadowOnly() {
return shadowOnly;
}
public Rectangle2D getBounds2D( BufferedImage src ) {
Rectangle r = new Rectangle(0, 0, src.getWidth(), src.getHeight());
if ( addMargins ) {
float xOffset = distance*(float)Math.cos(angle);
float yOffset = -distance*(float)Math.sin(angle);
r.width += (int)(Math.abs(xOffset)+2*radius);
r.height += (int)(Math.abs(yOffset)+2*radius);
}
return r;
}
public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
if ( dstPt == null )
dstPt = new Point2D.Double();
if ( addMargins ) {
float xOffset = distance*(float)Math.cos(angle);
float yOffset = -distance*(float)Math.sin(angle);
float topShadow = Math.max( 0, radius-yOffset );
float leftShadow = Math.max( 0, radius-xOffset );
dstPt.setLocation( srcPt.getX()+leftShadow, srcPt.getY()+topShadow );
} else
dstPt.setLocation( srcPt.getX(), srcPt.getY() );
return dstPt;
}
public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
int width = src.getWidth();
int height = src.getHeight();
if ( dst == null ) {
if ( addMargins ) {
ColorModel cm = src.getColorModel();
dst = new BufferedImage(cm, cm.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), cm.isAlphaPremultiplied(), null);
} else
dst = createCompatibleDestImage( src, null );
}
float shadowR = ((shadowColor >> 16) & 0xff) / 255f;
float shadowG = ((shadowColor >> 8) & 0xff) / 255f;
float shadowB = (shadowColor & 0xff) / 255f;
// Make a black mask from the image's alpha channel
float[][] extractAlpha = {
{ 0, 0, 0, shadowR },
{ 0, 0, 0, shadowG },
{ 0, 0, 0, shadowB },
{ 0, 0, 0, opacity }
};
BufferedImage shadow = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
new BandCombineOp( extractAlpha, null ).filter( src.getRaster(), shadow.getRaster() );
shadow = new GaussianFilter( radius ).filter( shadow, null );
float xOffset = distance*(float)Math.cos(angle);
float yOffset = -distance*(float)Math.sin(angle);
Graphics2D g = dst.createGraphics();
g.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, opacity ) );
if ( addMargins ) {
float radius2 = radius/2;
float topShadow = Math.max( 0, radius-yOffset );
float leftShadow = Math.max( 0, radius-xOffset );
g.translate( topShadow, leftShadow );
}
g.drawRenderedImage( shadow, AffineTransform.getTranslateInstance( xOffset, yOffset ) );
if ( !shadowOnly ) {
g.setComposite( AlphaComposite.SrcOver );
g.drawRenderedImage( src, null );
}
g.dispose();
return dst;
}
public String toString() {
return "Stylize/Drop Shadow...";
}
}