/*
Copyright 2006 Jerry Huxtable
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.jhlabs.image;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
/**
* A filter which produces motion blur the faster, but lower-quality way.
*/
public class MotionBlurOp extends AbstractBufferedImageOp implements MotionBlur {
private float centreX = 0.5f, centreY = 0.5f;
private float distance;
private float angle;
private float rotation;
private float zoom;
/**
* Construct a MotionBlurOp.
*/
public MotionBlurOp(String filterName) {
super(filterName);
}
/**
* Specifies the angle of blur.
*
* @param angle the angle of blur.
* @angle
* @see #getAngle
*/
@Override
public void setAngle(float angle) {
this.angle = angle;
}
/**
* Returns the angle of blur.
*
* @return the angle of blur.
* @see #setAngle
*/
@Override
public float getAngle() {
return angle;
}
/**
* Set the distance of blur.
*
* @param distance the distance of blur.
* @see #getDistance
*/
@Override
public void setDistance(float distance) {
this.distance = distance;
}
/**
* Get the distance of blur.
*
* @return the distance of blur.
* @see #setDistance
*/
@Override
public float getDistance() {
return distance;
}
/**
* Set the blur rotation.
*
* @param rotation the angle of rotation.
* @see #getRotation
*/
@Override
public void setRotation(float rotation) {
this.rotation = rotation;
}
/**
* Get the blur rotation.
*
* @return the angle of rotation.
* @see #setRotation
*/
@Override
public float getRotation() {
return rotation;
}
/**
* Set the blur zoom.
*
* @param zoom the zoom factor.
* @see #getZoom
*/
@Override
public void setZoom(float zoom) {
this.zoom = zoom;
}
/**
* Get the blur zoom.
*
* @return the zoom factor.
* @see #setZoom
*/
@Override
public float getZoom() {
return zoom;
}
/**
* Set the centre of the effect in the X direction as a proportion of the image size.
*
* @param centreX the center
* @see #getCentreX
*/
@Override
public void setCentreX(float centreX) {
this.centreX = centreX;
}
/**
* Get the centre of the effect in the X direction as a proportion of the image size.
*
* @return the center
* @see #setCentreX
*/
@Override
public float getCentreX() {
return centreX;
}
/**
* Set the centre of the effect in the Y direction as a proportion of the image size.
*
* @param centreY the center
* @see #getCentreY
*/
@Override
public void setCentreY(float centreY) {
this.centreY = centreY;
}
/**
* Get the centre of the effect in the Y direction as a proportion of the image size.
*
* @return the center
* @see #setCentreY
*/
@Override
public float getCentreY() {
return centreY;
}
/**
* Set the centre of the effect as a proportion of the image size.
*
* @param centre the center
* @see #getCentre
*/
@Override
public void setCentre(Point2D centre) {
this.centreX = (float) centre.getX();
this.centreY = (float) centre.getY();
}
/**
* Get the centre of the effect as a proportion of the image size.
*
* @return the center
* @see #setCentre
*/
@Override
public Point2D getCentre() {
return new Point2D.Float(centreX, centreY);
}
private static int log2(int n) {
int m = 1;
int log2n = 0;
while (m < n) {
m *= 2;
log2n++;
}
return log2n;
}
@Override
public BufferedImage filter(BufferedImage src, BufferedImage dst) {
if (dst == null) {
dst = createCompatibleDestImage(src, null);
}
BufferedImage tsrc = src;
float cx = (float) src.getWidth() * centreX;
float cy = (float) src.getHeight() * centreY;
float imageRadius = (float) Math.sqrt(cx * cx + cy * cy);
float translateX = (float) (distance * Math.cos(angle));
float translateY = (float) (distance * -Math.sin(angle));
float scale = zoom;
float rotate = rotation;
float maxDistance = distance + Math.abs(rotation * imageRadius) + zoom * imageRadius;
int steps = log2((int) maxDistance);
translateX /= maxDistance;
translateY /= maxDistance;
scale /= maxDistance;
rotate /= maxDistance;
if (steps == 0) {
Graphics2D g = dst.createGraphics();
g.drawRenderedImage(src, null);
g.dispose();
return dst;
}
pt = createProgressTracker(steps);
AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
BufferedImage tmp = createCompatibleDestImage(src, null);
for (int i = 0; i < steps; i++) {
Graphics2D g = tmp.createGraphics();
g.drawImage(tsrc, null, null);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setComposite(alphaComposite);
g.translate(cx + translateX, cy + translateY);
g.scale(1.0001 + scale, 1.0001 + scale); // The .0001 works round a bug on Windows where drawImage throws an ArrayIndexOutofBoundException
if (rotation != 0) {
g.rotate(rotate);
}
g.translate(-cx, -cy);
g.drawImage(dst, null, null);
g.dispose();
BufferedImage ti = dst;
dst = tmp;
tmp = ti;
tsrc = dst;
translateX *= 2;
translateY *= 2;
scale *= 2;
rotate *= 2;
pt.unitDone();
}
finishProgressTracker();
return dst;
}
public String toString() {
return "Blur/Faster Motion Blur...";
}
}