2

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);
    }
}

2 Answers2

0

Have you tried overriding paintComponent to invoke repaint (or maybe revalidate) on your image? This would ensure drawImage is called before anything is shown on screen after the change.

See also this question.

Community
  • 1
  • 1
mgibsonbr
  • 21,755
  • 7
  • 70
  • 112
-1

Not an answer, but a failed attempt that I thought I'd share. Down-vote if you must.

I thought I'd be clever and try to use a JScrollPane holding a JLabel/ImageIcon to do the movement for me, but it still didn't work. For e.g.,

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;

@SuppressWarnings("serial")
public class ImageViewer2 extends JPanel {
   private static final int SP_WIDTH = 500;
   private static final int SP_HEIGHT = SP_WIDTH;
   private JLabel viewportView = new JLabel();
   private JViewport viewport = new JViewport();
   private JScrollPane scrollpane = new JScrollPane();
   private Point viewLocOnScrn = null;

   public ImageViewer2() {
      viewport.setView(viewportView);
      scrollpane.setViewport(viewport);
      viewport.setBackground(Color.gray);
      viewport.addComponentListener(new MyComponentListener());

      scrollpane.setPreferredSize(new Dimension(SP_WIDTH, SP_HEIGHT));
      scrollpane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
      scrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);

      MyMouseAdapter mouseAdapter = new MyMouseAdapter();
      viewport.addMouseListener(mouseAdapter);
      viewport.addMouseMotionListener(mouseAdapter);

      JButton loadImageBtn = new JButton(new LoadImageAction("Load Image"));
      JPanel topPanel = new JPanel();
      topPanel.add(loadImageBtn);

      setLayout(new BorderLayout());
      add(scrollpane, BorderLayout.CENTER);
      add(topPanel, BorderLayout.PAGE_START);
   }

   private class MyComponentListener extends ComponentAdapter {

      @Override
      public void componentMoved(ComponentEvent compEvt) {
         compEvtOccurred(compEvt);
      }

      @Override
      public void componentResized(ComponentEvent compEvt) {
         compEvtOccurred(compEvt);
      }

      private void compEvtOccurred(ComponentEvent compEvt) {
         Point newViewLocOnScn = viewportView.getLocationOnScreen();
         if (viewLocOnScrn != null && !newViewLocOnScn.equals(viewLocOnScrn)) {
            Point scrollPaneLocOnScrn = scrollpane.getLocationOnScreen();
            int x = scrollPaneLocOnScrn.x - viewLocOnScrn.x;
            int y = scrollPaneLocOnScrn.y - viewLocOnScrn.y;
            viewport.setViewPosition(new Point(x, y));
         }
      }

   }

   private class MyMouseAdapter extends MouseAdapter {
      private Point delta;

      @Override
      public void mousePressed(MouseEvent mEvt) {
         Point mousePt = mEvt.getPoint();
         Point viewPos = viewport.getViewPosition();
         delta = new Point(viewPos.x + mousePt.x, viewPos.y + mousePt.y);
      }

      @Override
      public void mouseDragged(MouseEvent e) {
         if (delta ==  null ) {
            return;
         }

         Point p = e.getPoint();
         viewport.setViewPosition(new Point(delta.x - p.x, delta.y - p.y));
         viewLocOnScrn = viewportView.getLocationOnScreen();
      }

      @Override
      public void mouseReleased(MouseEvent e) {
         delta = null;
      }
   }

   private class LoadImageAction extends AbstractAction {
      public LoadImageAction(String text) {
         super(text);
      }

      @Override
      public void actionPerformed(ActionEvent actEvt) {
         JFileChooser jfc = new JFileChooser("img003.jpg");
         int jfcResult = jfc.showOpenDialog(ImageViewer2.this);
         if (jfcResult == JFileChooser.APPROVE_OPTION) {
            File file = jfc.getSelectedFile();
            try {
               BufferedImage bufImg = ImageIO.read(file);
               ImageIcon icon = new ImageIcon(bufImg);
               viewportView.setIcon(icon);
               Dimension vpSize = viewport.getSize();
               int vpX = (bufImg.getWidth() - vpSize.width) / 2;
               int vpY = (bufImg.getHeight() - vpSize.height) / 2;
               Point viewPos = new Point(vpX, vpY);
               viewport.setViewPosition(viewPos);
               viewLocOnScrn = viewportView.getLocationOnScreen();
            } catch (IOException e) {
               e.printStackTrace();
            }

         }

      }
   }

   private static void createAndShowGui() {
      ImageViewer2 mainPanel = new ImageViewer2();

      JFrame frame = new JFrame("Image Viewer 2");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

Comments and criticisms welcome!

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373