I am trying to write a reusable JPanel image viewer using Java 6/7. I have no trouble implementing image drag using mouseDragged nor resizing the JPanel to crop or reveal the image provided that the upper left corner of the JPanel (viewport origin) is not moved during the resize. Note I do not want to resize the image to fit the panel. I want the image to remain fixed to the screen while I resize the panel to crop it.
If I am dragging the left edge of the frame and thus moving the viewport origin, there is considerable image jitter during the resize operation even if I dynamically recompute the image origin during resize. This appears to be due to a change in viewport origin between the time when paintComponent() is called and the time when the image actually gets rendered through a call to drawImage(). Any thoughts on how to eliminate said jitter? Thanks in advance.
The two classes below should demonstrate the behavior:
/***** ImageViewer.java *****/
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import javax.imageio.ImageIO;
public class ImageViewer extends JFrame
{
private ImagePanel canvas;
private BufferedImage theImage;
private JFileChooser theChooser;
public ImageViewer( BufferedImage bi )
{
canvas= new ImagePanel( );
add( canvas, BorderLayout.CENTER );
JPanel control= new JPanel();
JButton loadBtn= new JButton( "Load Image" );
control.add( loadBtn );
add( control, BorderLayout.NORTH );
loadBtn.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e )
{ doLoad(); }
});
theChooser= new JFileChooser();
}
public void doLoad()
{
File iFile = null;
int retVal = theChooser.showOpenDialog(this);
if (retVal == JFileChooser.APPROVE_OPTION) {
iFile = theChooser.getSelectedFile();
try {
theImage = ImageIO.read(iFile);
} catch (FileNotFoundException ie) {
System.err.println("File not found");
System.exit(1);
} catch (IOException ie) {
System.err.println("IOException");
System.exit(1);
}
canvas.setImage( theImage );
repaint();
}
}
/**
* @param args
*/
public static void main(String[] args)
{
BufferedImage bImage= null;
ImageViewer theApp= new ImageViewer( bImage );
theApp.setDefaultCloseOperation(EXIT_ON_CLOSE);
theApp.pack();
theApp.setVisible( true );
}
}
/***** ImagePanel.java *****/
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.JPanel;
public class ImagePanel extends JPanel
{
private BufferedImage theImage;
private Dimension preferredSize;
private double scaleF = 1.0; // image scaling factor
private Point2D.Double deltaLoc; // dx, dy in scaled units for image upper
// left corner from origin to current loc
// inside or outside viewport
private Point panelLastLoc; // last location of upper left corner of the
// viewport in screen coordinates
private int lastX;
private int lastY;
private PanelState state = PanelState.IDLE;
public enum PanelState
{
IDLE, RESIZING, PMOVING, IMOVING
};
public ImagePanel()
{
setBackground(Color.GRAY);
preferredSize = new Dimension(400, 400);
addHierarchyListener(new HierarchyListener() {
public void hierarchyChanged(HierarchyEvent he)
{
doComponentChanged(he);
}
});
addHierarchyBoundsListener(new HierarchyBoundsAdapter() {
public void ancestorMoved(HierarchyEvent he)
{
doComponentMoved();
}
public void ancestorResized(HierarchyEvent he)
{
doAncestorResizing();
}
});
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent ce)
{
doComponentResized();
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me)
{
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
state = PanelState.IMOVING;
lastX = me.getX();
lastY = me.getY();
}
public void mouseReleased(MouseEvent me)
{
setCursor(Cursor.getDefaultCursor());
state = PanelState.IDLE;
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent me)
{
doImageMoved(me);
}
});
}
public ImagePanel(BufferedImage bi)
{
theImage = bi;
preferredSize = new Dimension(theImage.getWidth(), theImage.getHeight());
}
public Dimension getPreferredSize()
{
return preferredSize;
}
private void doComponentChanged(HierarchyEvent he)
{
if (isShowing()) {
panelLastLoc = getLocationOnScreen();
}
state = PanelState.IDLE;
}
private void doImageMoved(MouseEvent me)
{
if (!isShowing() || theImage == null)
return;
switch (state)
{
case RESIZING:
error("IMOVING->RESIZING");
break;
case PMOVING:
error("IMOVING->PMOVING");
break;
case IDLE:
error("IMOVING->IDLE");
break;
case IMOVING:
if (contains(me.getX(), me.getY())) {
int dx = me.getX() - lastX;
int dy = me.getY() - lastY;
lastX = me.getX();
lastY = me.getY();
deltaLoc.x += dx;
deltaLoc.y += dy;
repaint();
}
return;
}
}
private void doComponentMoved()
{
if (!isShowing() || theImage == null)
return;
switch (state)
{
case RESIZING:
return;
case IMOVING:
error("IMOVING->PMOVING");
break;
case PMOVING:
case IDLE:
panelLastLoc = getLocationOnScreen();
state = PanelState.PMOVING;
}
return;
}
private void doAncestorResizing()
{
if (!isShowing() || theImage == null)
return;
switch (state)
{
case IMOVING:
error("IMOVING->RESIZING");
return;
case RESIZING:
case PMOVING:
case IDLE:
Point cLoc = getLocationOnScreen();
state = PanelState.RESIZING;
if (theImage != null) {
deltaLoc.x -= cLoc.x - panelLastLoc.x;
deltaLoc.y -= cLoc.y - panelLastLoc.y;
}
panelLastLoc = cLoc;
}
}
private void doComponentResized()
{
Point cLoc = getLocationOnScreen();
switch (state)
{
case RESIZING:
state = PanelState.IDLE;
break;
case IMOVING:
error("IMOVING->RESIZED");
break;
case PMOVING:
state = PanelState.IDLE;
break;
case IDLE:
break;
}
if (theImage != null) {
deltaLoc.x -= cLoc.x - panelLastLoc.x;
deltaLoc.y -= cLoc.y - panelLastLoc.y;
}
panelLastLoc = cLoc;
}
public void setImage(BufferedImage nI)
{
theImage = nI;
scaleF = 1.0;
state = PanelState.IDLE;
deltaLoc = new Point2D.Double(Math.round((getWidth() - theImage.getWidth()) / 2.0),
Math.round((getHeight() - theImage.getHeight()) / 2.0));
panelLastLoc = getLocationOnScreen();
}
private void error(String em)
{
System.err.println(em);
System.exit(1);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (theImage == null)
return;
/*
* Check if the viewport has resized or moved. The check below is
* performed on every repaint.
*/
Point cLoc = getLocationOnScreen();
Dimension cDim = getSize();
if (state == PanelState.RESIZING || state == PanelState.IDLE) {
deltaLoc.x -= cLoc.x - panelLastLoc.x;
deltaLoc.y -= cLoc.y - panelLastLoc.y;
}
/*
System.err.println("image left edge= "
+ Math.round(cLoc.x + deltaLoc.x) + "; state= " + state
+ "; cDim= " + cDim + "; cLoc= " + cLoc + "; pLLoc= "
+ panelLastLoc + "; dLoc= " + deltaLoc);
*/
// In all cases update panelLastLoc
panelLastLoc = cLoc;
g2.drawImage(theImage, (int) Math.round(deltaLoc.x),
(int) Math.round(deltaLoc.y), null);
}
}