Archive

Archive for September, 2010

Intuitive Zoom with JScrollPane

September 2nd, 2010 2 comments

The problem: Display an image inside a panel and handle different zoom levels. The best approach is to use an already made component. If a component is not available or you just have to write it using only the basic Swing components than might help you save some time.

One approach is to hold a BufferedImage and the zoom level(integer, initial 100) and draw the image with appropriate settings in a JPanel. Overwrite the paintComponent function of a JPanel. Set the size of the panel to match the size of the zoomed image. Add this panel inside a JScrollPane and you have an initial version. Enable double buffering for the resulting panel to have a smooth appearance.

I prefer this approach instead of using a zoomed copy of the image as it saves a lot of memory.

The positions on the drawing panel match the positions on the zoomed image. The coordinates in the original image are calculated using a simple formula:

int imageX = panelX * 100/zoom;

If the zoomed image is smaller than the container than the image will be in the top right corner. Most users will want it in the center of the container. The solution is to wrap the drawing panel inside a centering panel. There a couple of solutions using a custom layout manager, a combination of Box layouts, SpringLayout, etc. A simple solution is the GridBagLayout:

JPanel centeringPanel = new JPanel(new GridBagLayout());
centeringPanel.add(drawingPanel, new GridBagConstraints());

This will center the drawing panel vertically and horizontally.

When changing the zoom, change the size of the drawing panel and call invalidate() and repaint() on the scroll pane view port. Now you have zoom in and zoom out.

You will notice a small problem. The zoom is not very intuitive, it will move the image to the top left corner. A more usable panel would zoom in/out preserving the view on the same area of the image. You need to calculate the new view position. There a couple of ways to do this I will present one of them:

// Save the previous coordinates
int oldZoom = zoom;
Rectangle oldView = scrollPane.getViewport().getViewRect();
// resize the panel for the new zoom
....
// calculate the new view position
Point newViewPos = new Point();
newViewPos.x = Math.max(0, (oldView.x + oldView.width / 2) * newZoom / oldZoom - oldView.width / 2);
newViewPos.y = Math.max(0, (oldView.y + oldView.height / 2) * newZoom / oldZoom - oldView.height / 2);
scrollPane.getViewport().setViewPosition(newViewPos);

You can add many usability improvements and optimizations but what I described here is an easy to implement and usable component that can be used in any application.

Categories: Programming Tags: , ,