How I can add JScroll bar to NavigableImagePanel which is an Image panel with an small navigation vi
Posted
by Sarah Kho
on Stack Overflow
See other posts from Stack Overflow
or by Sarah Kho
Published on 2010-01-07T21:21:06Z
Indexed on
2010/05/14
0:04 UTC
Read the original article
Hit count: 854
Hi,
I have the following NavigableImagePanel, it is under BSD license and I found it in the web. What I want to do with this panel is as follow:
I want to add a JScrollPane to it in order to show images in their full size and let the users to re-center the image using the small navigation panel. Right now, the panel resize the images to fit them in the current panel size. I want it to load the image in its real size and let users to navigate to different parts of the image using the navigation panel.
Source code for the panel:
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* @author pxt
*
*/
public class NavigableImagePanel extends JPanel {
/**
* <p>Identifies a change to the zoom level.</p>
*/
public static final String ZOOM_LEVEL_CHANGED_PROPERTY = "zoomLevel";
/**
* <p>Identifies a change to the zoom increment.</p>
*/
public static final String ZOOM_INCREMENT_CHANGED_PROPERTY = "zoomIncrement";
/**
* <p>Identifies that the image in the panel has changed.</p>
*/
public static final String IMAGE_CHANGED_PROPERTY = "image";
private static final double SCREEN_NAV_IMAGE_FACTOR = 0.15; // 15% of panel's width
private static final double NAV_IMAGE_FACTOR = 0.3; // 30% of panel's width
private static final double HIGH_QUALITY_RENDERING_SCALE_THRESHOLD = 1.0;
private static final Object INTERPOLATION_TYPE =
RenderingHints.VALUE_INTERPOLATION_BILINEAR;
private double zoomIncrement = 0.2;
private double zoomFactor = 1.0 + zoomIncrement;
private double navZoomFactor = 1.0 + zoomIncrement;
private BufferedImage image;
private BufferedImage navigationImage;
private int navImageWidth;
private int navImageHeight;
private double initialScale = 0.0;
private double scale = 0.0;
private double navScale = 0.0;
private int originX = 0;
private int originY = 0;
private Point mousePosition;
private Dimension previousPanelSize;
private boolean navigationImageEnabled = true;
private boolean highQualityRenderingEnabled = true;
private WheelZoomDevice wheelZoomDevice = null;
private ButtonZoomDevice buttonZoomDevice = null;
/**
* <p>Defines zoom devices.</p>
*/
public static class ZoomDevice {
/**
* <p>Identifies that the panel does not implement zooming,
* but the component using the panel does (programmatic zooming method).</p>
*/
public static final ZoomDevice NONE = new ZoomDevice("none");
/**
* <p>Identifies the left and right mouse buttons as the zooming device.</p>
*/
public static final ZoomDevice MOUSE_BUTTON = new ZoomDevice("mouseButton");
/**
* <p>Identifies the mouse scroll wheel as the zooming device.</p>
*/
public static final ZoomDevice MOUSE_WHEEL = new ZoomDevice("mouseWheel");
private String zoomDevice;
private ZoomDevice(String zoomDevice) {
this.zoomDevice = zoomDevice;
}
public String toString() {
return zoomDevice;
}
}
//This class is required for high precision image coordinates translation.
private class Coords {
public double x;
public double y;
public Coords(double x, double y) {
this.x = x;
this.y = y;
}
public int getIntX() {
return (int)Math.round(x);
}
public int getIntY() {
return (int)Math.round(y);
}
public String toString() {
return "[Coords: x=" + x + ",y=" + y + "]";
}
}
private class WheelZoomDevice implements MouseWheelListener {
public void mouseWheelMoved(MouseWheelEvent e) {
Point p = e.getPoint();
boolean zoomIn = (e.getWheelRotation() < 0);
if (isInNavigationImage(p)) {
if (zoomIn) {
navZoomFactor = 1.0 + zoomIncrement;
} else {
navZoomFactor = 1.0 - zoomIncrement;
}
zoomNavigationImage();
} else if (isInImage(p)) {
if (zoomIn) {
zoomFactor = 1.0 + zoomIncrement;
} else {
zoomFactor = 1.0 - zoomIncrement;
}
zoomImage();
}
}
}
private class ButtonZoomDevice extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
if (SwingUtilities.isRightMouseButton(e)) {
if (isInNavigationImage(p)) {
navZoomFactor = 1.0 - zoomIncrement;
zoomNavigationImage();
} else if (isInImage(p)) {
zoomFactor = 1.0 - zoomIncrement;
zoomImage();
}
} else {
if (isInNavigationImage(p)) {
navZoomFactor = 1.0 + zoomIncrement;
zoomNavigationImage();
} else if (isInImage(p)) {
zoomFactor = 1.0 + zoomIncrement;
zoomImage();
}
}
}
}
/**
* <p>Creates a new navigable image panel with no default image and
* the mouse scroll wheel as the zooming device.</p>
*/
public NavigableImagePanel() {
setOpaque(false);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
if (scale > 0.0) {
if (isFullImageInPanel()) {
centerImage();
} else if (isImageEdgeInPanel()) {
scaleOrigin();
}
if (isNavigationImageEnabled()) {
createNavigationImage();
}
repaint();
}
previousPanelSize = getSize();
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
if (isInNavigationImage(e.getPoint())) {
Point p = e.getPoint();
displayImageAt(p);
}
}
}
public void mouseClicked(MouseEvent e){
if (e.getClickCount() == 2) {
resetImage();
}
}
});
addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)
&& !isInNavigationImage(e.getPoint())) {
Point p = e.getPoint();
moveImage(p);
}
}
public void mouseMoved(MouseEvent e) {
//we need the mouse position so that after zooming
//that position of the image is maintained
mousePosition = e.getPoint();
}
});
setZoomDevice(ZoomDevice.MOUSE_WHEEL);
}
/**
* <p>Creates a new navigable image panel with the specified image
* and the mouse scroll wheel as the zooming device.</p>
*/
public NavigableImagePanel(BufferedImage image) throws IOException {
this();
setImage(image);
}
private void addWheelZoomDevice() {
if (wheelZoomDevice == null) {
wheelZoomDevice = new WheelZoomDevice();
addMouseWheelListener(wheelZoomDevice);
}
}
private void addButtonZoomDevice() {
if (buttonZoomDevice == null) {
buttonZoomDevice = new ButtonZoomDevice();
addMouseListener(buttonZoomDevice);
}
}
private void removeWheelZoomDevice() {
if (wheelZoomDevice != null) {
removeMouseWheelListener(wheelZoomDevice);
wheelZoomDevice = null;
}
}
private void removeButtonZoomDevice() {
if (buttonZoomDevice != null) {
removeMouseListener(buttonZoomDevice);
buttonZoomDevice = null;
}
}
/**
* <p>Sets a new zoom device.</p>
*
* @param newZoomDevice specifies the type of a new zoom device.
*/
public void setZoomDevice(ZoomDevice newZoomDevice) {
if (newZoomDevice == ZoomDevice.NONE) {
removeWheelZoomDevice();
removeButtonZoomDevice();
} else if (newZoomDevice == ZoomDevice.MOUSE_BUTTON) {
removeWheelZoomDevice();
addButtonZoomDevice();
} else if (newZoomDevice == ZoomDevice.MOUSE_WHEEL) {
removeButtonZoomDevice();
addWheelZoomDevice();
}
}
/**
* <p>Gets the current zoom device.</p>
*/
public ZoomDevice getZoomDevice() {
if (buttonZoomDevice != null) {
return ZoomDevice.MOUSE_BUTTON;
} else if (wheelZoomDevice != null) {
return ZoomDevice.MOUSE_WHEEL;
} else {
return ZoomDevice.NONE;
}
}
//Called from paintComponent() when a new image is set.
private void initializeParams() {
double xScale = (double)getWidth() / image.getWidth();
double yScale = (double)getHeight() / image.getHeight();
initialScale = Math.min(xScale, yScale);
scale = initialScale;
//An image is initially centered
centerImage();
if (isNavigationImageEnabled()) {
createNavigationImage();
}
}
//Centers the current image in the panel.
private void centerImage() {
originX = (int)(getWidth() - getScreenImageWidth()) / 2;
originY = (int)(getHeight() - getScreenImageHeight()) / 2;
}
//Creates and renders the navigation image in the upper let corner of the panel.
private void createNavigationImage() {
//We keep the original navigation image larger than initially
//displayed to allow for zooming into it without pixellation effect.
navImageWidth = (int)(getWidth() * NAV_IMAGE_FACTOR);
navImageHeight = navImageWidth * image.getHeight() / image.getWidth();
int scrNavImageWidth = (int)(getWidth() * SCREEN_NAV_IMAGE_FACTOR);
int scrNavImageHeight = scrNavImageWidth * image.getHeight() / image.getWidth();
navScale = (double)scrNavImageWidth / navImageWidth;
navigationImage = new BufferedImage(navImageWidth, navImageHeight,
image.getType());
Graphics g = navigationImage.getGraphics();
g.drawImage(image, 0, 0, navImageWidth, navImageHeight, null);
}
/**
* <p>Sets an image for display in the panel.</p>
*
* @param image an image to be set in the panel
*/
public void setImage(BufferedImage image) {
BufferedImage oldImage = this.image;
this.image = image;
//Reset scale so that initializeParameters() is called in paintComponent()
//for the new image.
scale = 0.0;
firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image);
repaint();
}
/**
* <p>resets an image to the centre of the panel</p>
*
*/
public void resetImage() {
BufferedImage oldImage = this.image;
this.image = image;
//Reset scale so that initializeParameters() is called in paintComponent()
//for the new image.
scale = 0.0;
firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image);
repaint();
}
/**
* <p>Tests whether an image uses the standard RGB color space.</p>
*/
public static boolean isStandardRGBImage(BufferedImage bImage) {
return bImage.getColorModel().getColorSpace().isCS_sRGB();
}
//Converts this panel's coordinates into the original image coordinates
private Coords panelToImageCoords(Point p) {
return new Coords((p.x - originX) / scale, (p.y - originY) / scale);
}
//Converts the original image coordinates into this panel's coordinates
private Coords imageToPanelCoords(Coords p) {
return new Coords((p.x * scale) + originX, (p.y * scale) + originY);
}
//Converts the navigation image coordinates into the zoomed image coordinates
private Point navToZoomedImageCoords(Point p) {
int x = p.x * getScreenImageWidth() / getScreenNavImageWidth();
int y = p.y * getScreenImageHeight() / getScreenNavImageHeight();
return new Point(x, y);
}
//The user clicked within the navigation image and this part of the image
//is displayed in the panel.
//The clicked point of the image is centered in the panel.
private void displayImageAt(Point p) {
Point scrImagePoint = navToZoomedImageCoords(p);
originX = -(scrImagePoint.x - getWidth() / 2);
originY = -(scrImagePoint.y - getHeight() / 2);
repaint();
}
//Tests whether a given point in the panel falls within the image boundaries.
private boolean isInImage(Point p) {
Coords coords = panelToImageCoords(p);
int x = coords.getIntX();
int y = coords.getIntY();
return (x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight());
}
//Tests whether a given point in the panel falls within the navigation image
//boundaries.
private boolean isInNavigationImage(Point p) {
return (isNavigationImageEnabled() && p.x < getScreenNavImageWidth()
&& p.y < getScreenNavImageHeight());
}
//Used when the image is resized.
private boolean isImageEdgeInPanel() {
if (previousPanelSize == null) {
return false;
}
return (originX > 0 && originX < previousPanelSize.width
|| originY > 0 && originY < previousPanelSize.height);
}
//Tests whether the image is displayed in its entirety in the panel.
private boolean isFullImageInPanel() {
return (originX >= 0 && (originX + getScreenImageWidth()) < getWidth()
&& originY >= 0 && (originY + getScreenImageHeight()) < getHeight());
}
/**
* <p>Indicates whether the high quality rendering feature is enabled.</p>
*
* @return true if high quality rendering is enabled, false otherwise.
*/
public boolean isHighQualityRenderingEnabled() {
return highQualityRenderingEnabled;
}
/**
* <p>Enables/disables high quality rendering.</p>
*
* @param enabled enables/disables high quality rendering
*/
public void setHighQualityRenderingEnabled(boolean enabled) {
highQualityRenderingEnabled = enabled;
}
//High quality rendering kicks in when when a scaled image is larger
//than the original image. In other words,
//when image decimation stops and interpolation starts.
private boolean isHighQualityRendering() {
return (highQualityRenderingEnabled
&& scale > HIGH_QUALITY_RENDERING_SCALE_THRESHOLD);
}
/**
* <p>Indicates whether navigation image is enabled.<p>
*
* @return true when navigation image is enabled, false otherwise.
*/
public boolean isNavigationImageEnabled() {
return navigationImageEnabled;
}
/**
* <p>Enables/disables navigation with the navigation image.</p>
* <p>Navigation image should be disabled when custom, programmatic navigation
* is implemented.</p>
*
* @param enabled true when navigation image is enabled, false otherwise.
*/
public void setNavigationImageEnabled(boolean enabled) {
navigationImageEnabled = enabled;
repaint();
}
//Used when the panel is resized
private void scaleOrigin() {
originX = originX * getWidth() / previousPanelSize.width;
originY = originY * getHeight() / previousPanelSize.height;
repaint();
}
//Converts the specified zoom level to scale.
private double zoomToScale(double zoom) {
return initialScale * zoom;
}
/**
* <p>Gets the current zoom level.</p>
*
* @return the current zoom level
*/
public double getZoom() {
return scale / initialScale;
}
/**
* <p>Sets the zoom level used to display the image.</p>
* <p>This method is used in programmatic zooming. The zooming center is
* the point of the image closest to the center of the panel.
* After a new zoom level is set the image is repainted.</p>
*
* @param newZoom the zoom level used to display this panel's image.
*/
public void setZoom(double newZoom) {
Point zoomingCenter = new Point(getWidth() / 2, getHeight() / 2);
setZoom(newZoom, zoomingCenter);
}
/**
* <p>Sets the zoom level used to display the image, and the zooming center,
* around which zooming is done.</p>
* <p>This method is used in programmatic zooming.
* After a new zoom level is set the image is repainted.</p>
*
* @param newZoom the zoom level used to display this panel's image.
*/
public void setZoom(double newZoom, Point zoomingCenter) {
Coords imageP = panelToImageCoords(zoomingCenter);
if (imageP.x < 0.0) {
imageP.x = 0.0;
}
if (imageP.y < 0.0) {
imageP.y = 0.0;
}
if (imageP.x >= image.getWidth()) {
imageP.x = image.getWidth() - 1.0;
}
if (imageP.y >= image.getHeight()) {
imageP.y = image.getHeight() - 1.0;
}
Coords correctedP = imageToPanelCoords(imageP);
double oldZoom = getZoom();
scale = zoomToScale(newZoom);
Coords panelP = imageToPanelCoords(imageP);
originX += (correctedP.getIntX() - (int)panelP.x);
originY += (correctedP.getIntY() - (int)panelP.y);
firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom),
new Double(getZoom()));
repaint();
}
/**
* <p>Gets the current zoom increment.</p>
*
* @return the current zoom increment
*/
public double getZoomIncrement() {
return zoomIncrement;
}
/**
* <p>Sets a new zoom increment value.</p>
*
* @param newZoomIncrement new zoom increment value
*/
public void setZoomIncrement(double newZoomIncrement) {
double oldZoomIncrement = zoomIncrement;
zoomIncrement = newZoomIncrement;
firePropertyChange(ZOOM_INCREMENT_CHANGED_PROPERTY,
new Double(oldZoomIncrement), new Double(zoomIncrement));
}
//Zooms an image in the panel by repainting it at the new zoom level.
//The current mouse position is the zooming center.
private void zoomImage() {
Coords imageP = panelToImageCoords(mousePosition);
double oldZoom = getZoom();
scale *= zoomFactor;
Coords panelP = imageToPanelCoords(imageP);
originX += (mousePosition.x - (int)panelP.x);
originY += (mousePosition.y - (int)panelP.y);
firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom),
new Double(getZoom()));
repaint();
}
//Zooms the navigation image
private void zoomNavigationImage() {
navScale *= navZoomFactor;
repaint();
}
/**
* <p>Gets the image origin.</p>
* <p>Image origin is defined as the upper, left corner of the image in
* the panel's coordinate system.</p>
* @return the point of the upper, left corner of the image in the panel's coordinates
* system.
*/
public Point getImageOrigin() {
return new Point(originX, originY);
}
/**
* <p>Sets the image origin.</p>
* <p>Image origin is defined as the upper, left corner of the image in
* the panel's coordinate system. After a new origin is set, the image is repainted.
* This method is used for programmatic image navigation.</p>
© Stack Overflow or respective owner