Question

The code that I have here is using a MouseAdapter to listen for the user to "draw" a box around the area of an image that they would like to zoom in on and calculate the ratio of the box to the image. It then resizes the image to the calculated ratio. This part works.

The issue that I am having is making the JScrollPane view appear as if it is still at the same top left position after the image has been resized. I have tried several methods that seem to have gotten close to the result I want but not exactly.

This is the Listener that finds the scale ratio and sets the position:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.Graphics;
import java.awt.Point;

import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.JComponent;

public class DynamicZoom extends MouseAdapter {

private Point start;
private Point end;
private double zoom = 1.0;
private JScrollPane pane;
private JViewport port;


public void mousePressed(MouseEvent e) {
    if(e.getButton() == MouseEvent.BUTTON1) {
        this.pane = (JScrollPane)e.getSource();
        this.port = pane.getViewport();
        start = e.getPoint();
    }
}

public void mouseReleased(MouseEvent e) {
    if(this.pane != null) {
        Point curr = this.port.getViewPosition();
        end = e.getPoint();

        ImageComponent canvas = (ImageComponent)this.port.getView();
        zoom = canvas.getScale();
        double factor = 0.0;
        double selectedWidth = Math.abs(end.getX() - start.getX());
        double selectedHeight = Math.abs(end.getY() - start.getY());
        if(selectedWidth > selectedHeight)
            factor = this.port.getWidth() / selectedWidth;
        else
            factor = this.port.getHeight() / selectedHeight;

        zoom *= factor;
        int x = (int)((start.x+curr.x)*zoom);
        int y = (int)((start.y+curr.y)*zoom);

        Point point = new Point(x, y);

        ((ImageComponent)(this.port.getView())).setScale(zoom);

        ResizeViewport.resize(pane);
        this.port.setViewPosition(point);
    }
}

public void mouseDragged(MouseEvent e) {
    if(this.pane != null) {
        Graphics g = this.port.getGraphics();
        int width = this.start.x - e.getX();
        int height = this.start.y - e.getY();

        int w = Math.abs( width );
        int h = Math.abs( height );
        int x = width < 0 ? this.start.x : e.getX();
        int y = height < 0 ? this.start.y : e.getY();
        g.drawRect(x, y, w, h);
        this.port.repaint();
    }
}

}

This is the ImageComponent it resizes and displays the image:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;

public class ImageComponent extends JComponent {
private static final long serialVersionUID = 1975488835382044371L;
private BufferedImage img = null;

private double scale = 0.0;

public ImageComponent() {}

public ImageComponent(BufferedImage img) {
    this.displayPage(img);
}

@Override
public void paint(Graphics g) {
    Graphics2D g2 = ((Graphics2D)g);
    if(this.img != null) {
        int width = (int)(this.img.getWidth() * this.scale);
        int height = (int)(this.img.getHeight() * this.scale);
        this.setPreferredSize(new Dimension(width, height));
        g2.drawImage(this.img, 0, 0, width, height, null, null);
        g2.dispose();
    }
}

public void displayPage(BufferedImage pic) {
    if(img != null) {
        this.img = pic;
    }
}

public BufferedImage getPage() {
    return this.img;
}

public void setScale(double ratio) {
    if(ratio > .04) {
        this.scale = ratio;
        this.repaint();
    }
}

public double getScale() {
    return this.scale;
}
}

The ResizeViewport forces the viewport to show the entire ImageComponent when it is scaled up because otherwise it will clip the image at the size that it previously was:

import java.awt.Dimension;

import javax.swing.JScrollPane;
public class ResizeViewport {
public static void resize(JScrollPane scroll) {
    int vw = scroll.getWidth()-scroll.getVerticalScrollBar().getWidth();
    int vh = scroll.getHeight()-scroll.getHorizontalScrollBar().getHeight();
    scroll.getViewport().setViewSize(new Dimension(vw, vh));
}
}
Was it helpful?

Solution

It turns out there is nothing wrong with the math used to calculate the position or the way I designed the code. The problem was that the ImageComponent was still painting in another Thread when the position was being calculated; therefore returning "false" values for the getWidth() and getHeight() methods. I solved the issue using the following code:

EventQueue.invokeLater(new Runnable() {
   public void run() {
      port.setViewPosition(new Point(x,y));
   }
});

This allows the painting to finish before trying to calculate the size of the image and setting the position in the JScrollPane. Problem solved. I would still like to throw a thanks out to the people that took the time to at least review this issue.

OTHER TIPS

I had a similar issue but sometimes the panel got painted wrong the first time after resize, after hours of trying to find a workaround I found a solution!

After a resize of the panel in the scrollpane it is best is to destroy viewport by setting to null, and then adding panel back to scrollpane that will recreate a new viewport and everything is working!

// Old viewport has to be replaced
scrollPane.setViewport(null);
scrollPane.setViewportView(zoomablePanel);

/Anders

The issue that I am having is making the JScrollPane view appear as if it is still at the same top left position after the image has been resized.

Sorry, I'm not sure what you're asking. I might be able to give an answer if you clarify a little.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top