package magic.ui.widget;
import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
@SuppressWarnings("serial")
public class ImagePanel extends JPanel {
public enum ScaleMode {AUTOMATIC, PERFORMANCE, QUALITY};
private ScaleMode scaleMode = ScaleMode.AUTOMATIC;
// If the display image has a diagonal length less than this
// value and scale mode is AUTOMATIC then render using re-sampling
// for improved image quality.
private int highQualityDiagonalMaximum = 415;
private BufferedImage sourceImage = null;
private BufferedImage scaledImage = null;
private Dimension sourceSizeWhenVertical;
private Dimension sourceSizeWhenHorizontal;
private double imageScale = 1.0;
private int degreesOfRotation = 0;
private boolean sizeToFit = false;
private boolean restrictToImageSize = false;
public ImagePanel() {
setOpaque(false);
};
public ImagePanel(BufferedImage image) {
this(image, 0);
}
public ImagePanel(BufferedImage image, int initialRotation) {
setImage(image);
setRotation(initialRotation);
setOpaque(false);
}
public void setImage(BufferedImage image) {
this.sourceImage = image;
this.sourceSizeWhenVertical = new Dimension(image.getWidth(), image.getHeight());
this.sourceSizeWhenHorizontal = new Dimension(image.getHeight(), image.getWidth());
}
public void setScaleMode(ScaleMode mode) {
this.scaleMode = mode;
}
public void repack() {
this.setSize(this.getDisplayedImageSize());
}
public double getDisplayedImageDiagonalLength() {
return Math.sqrt(Math.pow(this.getDisplayedImageDimensions().height, 2) + Math.pow(this.getDisplayedImageDimensions().width, 2));
}
/**
* The width and height of the image depend on its orientation.
*/
private Dimension getSourceImageDimensions() {
boolean isVerticalOrientation = (degreesOfRotation % 180 == 0);
return (isVerticalOrientation ? sourceSizeWhenVertical : sourceSizeWhenHorizontal);
}
/**
* Convenience function to show dimensions of panel using a dashed border.
*/
public void showBounds(boolean showBounds) {
setBorder(showBounds ? BorderFactory.createDashedBorder(null) : null);
}
/**
* Currently, will round to the nearest 90 degrees. This means the image
* can ONLY have either a vertical or horizontal orientation.
*/
public void setRotation(int degrees) {
this.degreesOfRotation = degrees; // getRotationToNearest(degrees, 90);
repaint();
}
public int getRotation() {
return this.degreesOfRotation;
}
private int getRotationToNearest(int requestedRotation, int nearestRotation) {
// Ensure requested rotation falls with -360..0..360 degree range first.
requestedRotation = requestedRotation - (360 * (requestedRotation / 360));
return (int)(Math.rint((double) requestedRotation / nearestRotation) * nearestRotation);
}
public Dimension getSourceImageSize(boolean asDisplayed) {
if (!asDisplayed) {
return this.sourceSizeWhenVertical;
} else {
return getSourceImageDimensions();
}
}
private Dimension getDisplayedImageDimensions() {
int w =(int) (getSourceImageDimensions().width * this.imageScale);
int h =(int) (getSourceImageDimensions().height * this.imageScale);
return new Dimension(w, h);
}
public Dimension getDisplayedImageSize() {
return getDisplayedImageDimensions();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (sourceImage != null) {
// if (this.scaleMode == ScaleMode.AUTOMATIC) {
// if (this.getDisplayedImageDiagonalLength() <= this.highQualityDiagonalMaximum) {
// paintWithResampling((Graphics2D) g);
// } else {
// paintWithoutResampling((Graphics2D) g);
// }
// } else if (this.scaleMode == ScaleMode.QUALITY) {
// paintWithResampling((Graphics2D) g);
// } else {
// paintWithoutResampling((Graphics2D) g);
// }
paintWithoutResampling((Graphics2D) g);
}
}
// private void paintWithResampling(Graphics2D g2d) {
// System.out.println("paintWithResampling()");
//
// if (sizeToFit) { setScaleToFit(); }
//
// if (this.imageScale != 1) {
// createScaledImage();
// if (scaledImage != null) {
// //System.out.println("Scaling");
// g2d.drawImage(scaledImage, getAffineTransform(scaledImage, false), null);
// }
// } else {
// //System.out.println("No scaling");
// g2d.drawImage(sourceImage, getAffineTransform(sourceImage, false), null);
// }
//
// }
// /**
// * Uses Morten Nobel's java-image-scaling library to resize image.
// * <p>
// * This produces superior quality to the affine scaling as image sizes
// * are reduced but at the cost of performance and increased cpu usage.
// * </p>
// * @throws InterruptedException
// * @throws InvocationTargetException
// */
// private void createScaledImage() {
// float scaling = (float) ImagePanel.this.imageScale;
// if (scaling != 1) {
// DimensionConstrain d = DimensionConstrain.createRelativeDimension(scaling);
// ResampleOp resampler = new ResampleOp(d);
// scaledImage = resampler.filter(sourceImage, null);
// } else {
// scaledImage = sourceImage;
// }
// }
/**
* Renders image without using any additional re-sampling.
* <p>
* For scales of approximately 0.6 or above this should produce more than acceptable results.
* For thumbnail size images there is a distinct degradation in quality and a resampling
* algorithm should be used if image quality is more important than performance.
* </p>
*/
private void paintWithoutResampling(Graphics2D g2d) {
// System.out.println("paintWithoutResampling()");
// These make a visible difference...
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
// ...not so sure about...
g2d.setComposite(AlphaComposite.Src);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(sourceImage, getAffineTransform(sourceImage, true), null);
}
/**
* Scale and rotate the image using an Affine transformation.
*
* @see
* <a href="http://stackoverflow.com/questions/4918482/rotating-bufferedimage-instances">rotating BufferedImage instances</a>
*
*/
private AffineTransform getAffineTransform(BufferedImage sourceImage, boolean createScaleTransform) {
// Note that transformations happen in reverse order.
AffineTransform at = new AffineTransform();
// 4. move image to the center of the component. Remember, image center point is (0,0).
at.translate(getWidth() / 2, getHeight() / 2);
// 3. rotate around (0,0) if applicable.
applyRotateTransform(at);
// 2. scale image.
if (createScaleTransform) {
if (sizeToFit) { setScaleToFit(); }
at.scale(this.imageScale, this.imageScale);
}
// 1. move the image so that its center is at (0,0).
at.translate(-sourceImage.getWidth() / 2, -sourceImage.getHeight() / 2);
return at;
}
private void applyRotateTransform(AffineTransform at) {
at.rotate(Math.toRadians((double) degreesOfRotation));
}
public double getScale() {
return this.imageScale;
}
/**
* Display image at its original size regardless of the panel size.
*/
public void sizeImageToOriginal() {
this.sizeToFit = false;
this.imageScale = 1.0;
repaint();
}
/**
* Display image at its original size unless the panel is too small
* in which case reduce size to fit (whilst retaining aspect ratio).
* <p>
* Image will not expand to fit a larger panel beyond its original size.
* </p>
*/
public void sizeImageToFitMaxOriginal() {
this.sizeToFit = true;
this.restrictToImageSize = true;
repaint();
}
/**
* Size image to a specified scale whilst retaining its aspect ratio.
* <p>
* Ignores panel dimensions and does not resize with panel.
* <br><br>
* Examples:<br>
* - a scale of 0.5 will display the image at half its size.<br>
* - a scale of 2.0 will display the image at twice its size.<br>
* - a scale of 1.0 will display the image at its original size.
* </p>
*/
public void sizeImageToScale(double newScale) {
this.sizeToFit = false;
this.restrictToImageSize = false;
this.imageScale = newScale;
repaint();
}
/**
* Image will resize to fit the panel whilst retaining its aspect ratio.
*/
public void sizeImageToFitPanel() {
this.sizeToFit = true;
this.restrictToImageSize = false;
repaint();
}
/**
* Maintains aspect ratio.
*/
private void setScaleToFit() {
int thisWidth = this.getWidth();
int thisHeight = this.getHeight();
int imageWidth = getSourceImageDimensions().width;
int imageHeight = getSourceImageDimensions().height;
int newWidth = thisWidth;
if (this.restrictToImageSize) {
if (newWidth > imageWidth) {
newWidth = imageWidth;
}
}
this.imageScale = (double)newWidth / imageWidth;
int newHeight = (int) (this.imageScale * imageHeight);
if (newHeight > thisHeight) {
newHeight = thisHeight;
this.imageScale = (double)newHeight / imageHeight;
}
}
public void clearImage() {
this.sourceImage = null;
this.scaledImage = null;
repaint();
}
}