package arcane.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* <p>ScaledImagePanel class.</p>
*
* @author Forge
* @version $Id: $
*/
public class ScaledImagePanel extends JPanel {
/** Constant <code>serialVersionUID=-5691107238620895385L</code> */
private static final long serialVersionUID = -5691107238620895385L;
public volatile Image srcImage;
public volatile Image srcImageBlurred;
private ScalingType scalingType = ScalingType.bilinear;
private boolean scaleLarger;
private MultipassType multiPassType = MultipassType.bilinear;
private boolean blur;
/**
* <p>Constructor for ScaledImagePanel.</p>
*/
public ScaledImagePanel() {
super(false);
setOpaque(false);
}
/**
* <p>setImage.</p>
*
* @param srcImage a {@link java.awt.Image} object.
* @param srcImageBlurred a {@link java.awt.Image} object.
*
*/
public void setImage(Image srcImage, Image srcImageBlurred) {
this.srcImage = srcImage;
this.srcImageBlurred = srcImageBlurred;
}
/**
* <p>clearImage.</p>
*/
public void clearImage() {
srcImage = null;
srcImageBlurred = null;
repaint();
}
/**
* <p>setScalingMultiPassType.</p>
*
* @param multiPassType a {@link arcane.ui.ScaledImagePanel.MultipassType} object.
*/
public void setScalingMultiPassType(MultipassType multiPassType) {
this.multiPassType = multiPassType;
}
/**
* <p>Setter for the field <code>scalingType</code>.</p>
*
* @param scalingType a {@link arcane.ui.ScaledImagePanel.ScalingType} object.
*/
public void setScalingType(ScalingType scalingType) {
this.scalingType = scalingType;
}
/**
* <p>setScalingBlur.</p>
*
* @param blur a boolean.
*/
public void setScalingBlur(boolean blur) {
this.blur = blur;
}
/**
* <p>Setter for the field <code>scaleLarger</code>.</p>
*
* @param scaleLarger a boolean.
*/
public void setScaleLarger(boolean scaleLarger) {
this.scaleLarger = scaleLarger;
}
/**
* <p>hasImage.</p>
*
* @return a boolean.
*/
public boolean hasImage() {
return srcImage != null;
}
/**
* <p>getScalingInfo.</p>
*
* @return a {@link arcane.ui.ScaledImagePanel.ScalingInfo} object.
*/
private ScalingInfo getScalingInfo() {
int panelWidth = getWidth();
int panelHeight = getHeight();
int srcWidth = srcImage.getWidth(null);
int srcHeight = srcImage.getHeight(null);
int targetWidth = srcWidth;
int targetHeight = srcHeight;
if (scaleLarger || srcWidth > panelWidth || srcHeight > panelHeight) {
targetWidth = Math.round(panelHeight * (srcWidth / (float) srcHeight));
if (targetWidth > panelWidth) {
targetHeight = Math.round(panelWidth * (srcHeight / (float) srcWidth));
targetWidth = panelWidth;
} else
targetHeight = panelHeight;
}
ScalingInfo info = new ScalingInfo();
info.targetWidth = targetWidth;
info.targetHeight = targetHeight;
info.srcWidth = srcWidth;
info.srcHeight = srcHeight;
info.x = panelWidth / 2 - targetWidth / 2;
info.y = panelHeight / 2 - targetHeight / 2;
return info;
}
/** {@inheritDoc} */
public void paint(Graphics g) {
if (srcImage == null) return;
Graphics2D g2 = (Graphics2D) g.create();
ScalingInfo info = getScalingInfo();
switch (scalingType) {
case nearestNeighbor:
scaleWithDrawImage(g2, info, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
break;
case bilinear:
scaleWithDrawImage(g2, info, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
break;
case bicubic:
scaleWithDrawImage(g2, info, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
break;
case areaAveraging:
scaleWithGetScaledInstance(g2, info, Image.SCALE_AREA_AVERAGING);
break;
case replicate:
scaleWithGetScaledInstance(g2, info, Image.SCALE_REPLICATE);
break;
}
}
/**
* <p>scaleWithGetScaledInstance.</p>
*
* @param g2 a {@link java.awt.Graphics2D} object.
* @param info a {@link arcane.ui.ScaledImagePanel.ScalingInfo} object.
* @param hints a int.
*/
private void scaleWithGetScaledInstance(Graphics2D g2, ScalingInfo info, int hints) {
Image srcImage = getSourceImage(info);
Image scaledImage = srcImage.getScaledInstance(info.targetWidth, info.targetHeight, hints);
g2.drawImage(scaledImage, info.x, info.y, null);
}
/**
* <p>scaleWithDrawImage.</p>
*
* @param g2 a {@link java.awt.Graphics2D} object.
* @param info a {@link arcane.ui.ScaledImagePanel.ScalingInfo} object.
* @param hint a {@link java.lang.Object} object.
*/
private void scaleWithDrawImage(Graphics2D g2, ScalingInfo info, Object hint) {
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
int tempDestWidth = info.srcWidth / 2, tempDestHeight = info.srcHeight / 2;
if (tempDestWidth < info.targetWidth) tempDestWidth = info.targetWidth;
if (tempDestHeight < info.targetHeight) tempDestHeight = info.targetHeight;
Image srcImage = getSourceImage(info);
// If not doing multipass or multipass only needs a single pass, just scale it once directly to the panel surface.
if (multiPassType == MultipassType.none || (tempDestWidth == info.targetWidth && tempDestHeight == info.targetHeight)) {
g2.drawImage(srcImage, info.x, info.y, info.targetWidth, info.targetHeight, null);
return;
}
BufferedImage tempImage = new BufferedImage(tempDestWidth, tempDestHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2temp = tempImage.createGraphics();
switch (multiPassType) {
case nearestNeighbor:
g2temp.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
break;
case bilinear:
g2temp.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
break;
case bicubic:
g2temp.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
break;
}
// Render first pass from image to temp.
g2temp.drawImage(srcImage, 0, 0, tempDestWidth, tempDestHeight, null);
// Render passes between the first and last pass.
int tempSrcWidth = tempDestWidth;
int tempSrcHeight = tempDestHeight;
while (true) {
if (tempDestWidth > info.targetWidth) {
tempDestWidth = tempDestWidth / 2;
if (tempDestWidth < info.targetWidth) tempDestWidth = info.targetWidth;
}
if (tempDestHeight > info.targetHeight) {
tempDestHeight = tempDestHeight / 2;
if (tempDestHeight < info.targetHeight) tempDestHeight = info.targetHeight;
}
if (tempDestWidth == info.targetWidth && tempDestHeight == info.targetHeight) break;
g2temp.drawImage(tempImage, 0, 0, tempDestWidth, tempDestHeight, 0, 0, tempSrcWidth, tempSrcHeight, null);
tempSrcWidth = tempDestWidth;
tempSrcHeight = tempDestHeight;
}
g2temp.dispose();
// Render last pass from temp to panel surface.
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(tempImage, info.x, info.y, info.x + info.targetWidth, info.y + info.targetHeight, 0, 0, tempSrcWidth,
tempSrcHeight, null);
}
/**
* <p>getSourceImage.</p>
*
* @param info a {@link arcane.ui.ScaledImagePanel.ScalingInfo} object.
* @return a {@link java.awt.Image} object.
*/
private Image getSourceImage(ScalingInfo info) {
if (!blur || srcImageBlurred == null) return srcImage;
if (info.srcWidth / 2 < info.targetWidth || info.srcHeight / 2 < info.targetHeight) return srcImage;
return srcImageBlurred;
}
static private class ScalingInfo {
public int targetWidth;
public int targetHeight;
public int srcWidth;
public int srcHeight;
public int x;
public int y;
}
static public enum MultipassType {
none, nearestNeighbor, bilinear, bicubic
}
static public enum ScalingType {
nearestNeighbor, replicate, bilinear, bicubic, areaAveraging
}
}