Zoomable JScrollPane - setViewPosition fails to update
Asked Answered
F

3

7

I'm trying to code a zoom-able image in a JScrollPane.

When the image is fully zoomed out it should be centered horizontally and vertically. When both scroll bars have appeared the zooming should always happen relative to the mouse coordinate, i.e. the same point of the image should be under the mouse before and after the zoom event.

I have almost achieves my goal. Unfortunately the "scrollPane.getViewport().setViewPosition()" method sometimes fails to update the view position correctly. Calling the method twice (hack!) overcomes the issue in most cases, but the view still flickers.

I have no explanation as to why this is happening. However I'm confident that it's not a math problem.


MWE

Below is a MWE. To see what my problem is in particular you can do the following:

  • Zoom in until you have some scroll bars (200% zoom or so)
  • Scroll into the bottom right corner by clicking the scroll bars
  • Place the mouse in the corner and zoom in twice. The second time you'll see how the scroll position jumps towards the center.

I would really appreciate if someone could tell me where the problem lies. Thank you!

package com.vitco;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * Zoom-able scroll panel test case
 */
public class ZoomScrollPanel {

    // the size of our image
    private final static int IMAGE_SIZE = 600;

    // create an image to display
    private BufferedImage getImage() {
        BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        // draw the small pixel first
        Random rand = new Random();
        for (int x = 0; x < IMAGE_SIZE; x += 10) {
            for (int y = 0; y < IMAGE_SIZE; y += 10) {
                g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
                g.fillRect(x, y, 10, 10);
            }
        }
        // draw the larger transparent pixel second
        for (int x = 0; x < IMAGE_SIZE; x += 100) {
            for (int y = 0; y < IMAGE_SIZE; y += 100) {
                g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
                g.fillRect(x, y, 100, 100);
            }
        }
        return image;
    }

    // the image panel that resizes according to zoom level
    private class ImagePanel extends JPanel {
        private final BufferedImage image = getImage();

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D)g.create();
            g2.scale(scale, scale);
            g2.drawImage(image, 0, 0, null);
            g2.dispose();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
        }
    }

    // the current zoom level (100 means the image is shown in original size)
    private double zoom = 100;
    // the current scale (scale = zoom/100)
    private double scale = 1;

    // the last seen scale
    private double lastScale = 1;

    public void alignViewPort(Point mousePosition) {
        // if the scale didn't change there is nothing we should do
        if (scale != lastScale) {
            // compute the factor by that the image zoom has changed
            double scaleChange = scale / lastScale;

            // compute the scaled mouse position
            Point scaledMousePosition = new Point(
                    (int)Math.round(mousePosition.x * scaleChange),
                    (int)Math.round(mousePosition.y * scaleChange)
            );

            // retrieve the current viewport position
            Point viewportPosition = scrollPane.getViewport().getViewPosition();

            // compute the new viewport position
            Point newViewportPosition = new Point(
                    viewportPosition.x + scaledMousePosition.x - mousePosition.x,
                    viewportPosition.y + scaledMousePosition.y - mousePosition.y
            );

            // update the viewport position
            // IMPORTANT: This call doesn't always update the viewport position. If the call is made twice
            // it works correctly. However the screen still "flickers".
            scrollPane.getViewport().setViewPosition(newViewportPosition);

            // debug
            if (!newViewportPosition.equals(scrollPane.getViewport().getViewPosition())) {
                System.out.println("Error: " + newViewportPosition + " != " + scrollPane.getViewport().getViewPosition());
            }

            // remember the last scale
            lastScale = scale;
        }
    }

    // reference to the scroll pane container
    private final JScrollPane scrollPane;

    // constructor
    public ZoomScrollPanel() {
        // initialize the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(600, 600);

        // initialize the components
        final ImagePanel imagePanel = new ImagePanel();
        final JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new GridBagLayout());
        centerPanel.add(imagePanel);
        scrollPane = new JScrollPane(centerPanel);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        frame.add(scrollPane);

        // add mouse wheel listener
        imagePanel.addMouseWheelListener(new MouseAdapter() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                super.mouseWheelMoved(e);
                // check the rotation of the mousewheel
                int rotation = e.getWheelRotation();
                boolean zoomed = false;
                if (rotation > 0) {
                    // only zoom out until no scrollbars are visible
                    if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
                            scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
                        zoom = zoom / 1.3;
                        zoomed = true;
                    }
                } else {
                    // zoom in until maximum zoom size is reached
                    double newCurrentZoom = zoom * 1.3;
                    if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
                        zoom = newCurrentZoom;
                        zoomed = true;
                    }
                }
                // check if a zoom happened
                if (zoomed) {
                    // compute the scale
                    scale = (float) (zoom / 100f);

                    // align our viewport
                    alignViewPort(e.getPoint());

                    // invalidate and repaint to update components
                    imagePanel.revalidate();
                    scrollPane.repaint();
                }
            }
        });

        // display our frame
        frame.setVisible(true);
    }

    // the main method
    public static void main(String[] args) {
        new ZoomScrollPanel();
    }
}

Note: I have also looked at the question here JScrollPane setViewPosition After "Zoom" but unfortunately the problem and solution are slightly different and do not apply.


Edit

I have solved the issue by using a hack, however I'm still no closer to understanding as to what the underlying problem is. What is happening is that when the setViewPosition is called some internal state changes trigger additional calls to setViewPosition. These additional calls only happen occasionally. When I'm blocking them everything works perfectly.

To fix the problem I simply introduced a new boolean variable "blocked = false;" and replaced the lines

    scrollPane = new JScrollPane(centerPanel);

and

    scrollPane.getViewport().setViewPosition(newViewportPosition);

with

    scrollPane = new JScrollPane();

    scrollPane.setViewport(new JViewport() {
        private boolean inCall = false;
        @Override
        public void setViewPosition(Point pos) {
            if (!inCall || !blocked) {
                inCall = true;
                super.setViewPosition(pos);
                inCall = false;
            }
        }
    });

    scrollPane.getViewport().add(centerPanel);

and

     blocked = true;
     scrollPane.getViewport().setViewPosition(newViewportPosition);
     blocked = false;

I would still really appreciate if someone could make sense of this!

Why does this hack work? Is there a cleaner way to achieve the same functionality?

Florance answered 26/3, 2014 at 1:14 Comment(10)
Is it intended that when the view is zoomed out maximally, the first zoom operation already zooms to a different location than the one my mouse is at?Inamorata
Edited the example and added scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); This should help with the initial jumping.Florance
It doesn't solve the initial jump, I added those too to see if the problem comes from calculating the scrollbars' size into the position (which it shouldn't) but it's not that. One thing which is curious already is that your image does not have a constant size. Set the frame to full screen size, zoom in maximally, resize the frame to a very smal size, then zoom out - you get a tiny image. Is this intentional?Inamorata
(1) So about the initial jump: The image has to jump to some degree since it should be centered when zoomed out. Once the scroll bars have appeared the zooming should always work correctly. Adding the scroll bars (my previous comment) means that the initial jump is not "to the center" but the the "border". So they result in exactly the effect that I want for the initial jump. (2) The image size depends on the zoom. The maximum amount you can zoom out depends on the window size (once the scroll bars would disappear you can no longer zoom out further). So the behavior is as expected.Florance
If you set the frame size to 200x200 instead of 600x600 the first zoom operation works as intended (edit: depending if the zoom location is far enough from the border). The problem seems to be in your calculation, not in the behavior of the setViewPosition. As to point (1), ignore for a moment zooming out. Is it not what you want to zoom into the pixel the mouse is at regardless of frame size and visibility of the scrollbars?Inamorata
OK, the location of the scrollbars (and their visibility) relative to the zoom-in point is what gives the effect you described, but it does what the code tells it to (I did the calculation and got the same that swing gets and not the same as you want to get). Once I'm clear on how you want this to behave I will supply the solution.Inamorata
Did you try the not-fixed version and follow the instructions above? That is the bug I encountered (the other behavior is as expected). The hack I have described results exactly in the functionality that I want (see last edit!), however I would like to know why it works and if there is a cleaner way to achieve the same functionality.Florance
I see it but as I said it's not a bug, it's the behavior it's supposed to have. I explained that what you do in your code is not centering on the mouse position. Put your mouse at a point on the top left square when the scrollbars are set to the top-left corner. Now pick a specific location and don't move the mouse, just scroll it. You will see that you get different x-y coordinates each scroll even though the mouse does not move. It's because the image size is not constant and it's coordinate system changes according to the viewport's size.Inamorata
I can explain with the example: (1) I put my mouse on a top-left corner and zoom in. The MouseWheelEvent occurs at x positions 105, 136, 177, 230 etc, which is the 1.3 zoom factor (y scales the same). You call this behavior intended. (2) I put my mouse on a bottom-right corner and zoom in. The x coordinates I get are 537, 698, 907 etc. - 1.3 factor. However, between 537 and 698 you print the line x=151 != x=34. You have one coordinate system which is scaling and one which isn't, and you're trying to adjust between them.Inamorata
I think we're talking about two different things. I created a video that shows the problem: youtube.com/watch?v=2Vhr2E0eLRM&hd=1 At 0:09 (after adjusting the scroll bars a tiny bit) I zoom in once into the bottom right corner and everything works perfectly. Then I do the same zoom step again and the view "jumps" to the center. Why is this happening?Florance
F
2

Here is the completed, fully functional Code. I still don't understand why the hack is necessary, but at least it now works as expected:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * Zoom-able scroll panel
 */
public class ZoomScrollPanel {

    // the size of our image
    private final static int IMAGE_SIZE = 600;

    // create an image to display
    private BufferedImage getImage() {
        BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        // draw the small pixel first
        Random rand = new Random();
        for (int x = 0; x < IMAGE_SIZE; x += 10) {
            for (int y = 0; y < IMAGE_SIZE; y += 10) {
                g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255)));
                g.fillRect(x, y, 10, 10);
            }
        }
        // draw the larger transparent pixel second
        for (int x = 0; x < IMAGE_SIZE; x += 100) {
            for (int y = 0; y < IMAGE_SIZE; y += 100) {
                g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
                g.fillRect(x, y, 100, 100);
            }
        }
        return image;
    }

    // the image panel that resizes according to zoom level
    private class ImagePanel extends JPanel {
        private final BufferedImage image = getImage();

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D)g.create();
            g2.scale(scale, scale);
            g2.drawImage(image, 0, 0, null);
            g2.dispose();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
        }
    }

    // the current zoom level (100 means the image is shown in original size)
    private double zoom = 100;
    // the current scale (scale = zoom/100)
    private double scale = 1;

    // the last seen scale
    private double lastScale = 1;

    // true if currently executing setViewPosition
    private boolean blocked = false;

    public void alignViewPort(Point mousePosition) {
        // if the scale didn't change there is nothing we should do
        if (scale != lastScale) {
            // compute the factor by that the image zoom has changed
            double scaleChange = scale / lastScale;

            // compute the scaled mouse position
            Point scaledMousePosition = new Point(
                    (int)Math.round(mousePosition.x * scaleChange),
                    (int)Math.round(mousePosition.y * scaleChange)
            );

            // retrieve the current viewport position
            Point viewportPosition = scrollPane.getViewport().getViewPosition();

            // compute the new viewport position
            Point newViewportPosition = new Point(
                    viewportPosition.x + scaledMousePosition.x - mousePosition.x,
                    viewportPosition.y + scaledMousePosition.y - mousePosition.y
            );

            // update the viewport position
            blocked = true;
            scrollPane.getViewport().setViewPosition(newViewportPosition);
            blocked = false;

            // remember the last scale
            lastScale = scale;
        }
    }

    // reference to the scroll pane container
    private final JScrollPane scrollPane;

    // constructor
    public ZoomScrollPanel() {
        // initialize the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(600, 600);

        // initialize the components
        final ImagePanel imagePanel = new ImagePanel();
        final JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new GridBagLayout());
        centerPanel.add(imagePanel);
        scrollPane = new JScrollPane();

        scrollPane.setViewport(new JViewport() {
            private boolean inCall = false;
            @Override
            public void setViewPosition(Point pos) {
                if (!inCall || !blocked) {
                    inCall = true;
                    super.setViewPosition(pos);
                    inCall = false;
                }
            }
        });

        scrollPane.getViewport().add(centerPanel);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        frame.add(scrollPane);

        // add mouse wheel listener
        imagePanel.addMouseWheelListener(new MouseAdapter() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                super.mouseWheelMoved(e);
                // check the rotation of the mousewheel
                int rotation = e.getWheelRotation();
                boolean zoomed = false;
                if (rotation > 0) {
                    // only zoom out until no scrollbars are visible
                    if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
                            scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
                        zoom = zoom / 1.3;
                        zoomed = true;
                    }
                } else {
                    // zoom in until maximum zoom size is reached
                    double newCurrentZoom = zoom * 1.3;
                    if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
                        zoom = newCurrentZoom;
                        zoomed = true;
                    }
                }
                // check if a zoom happened
                if (zoomed) {
                    // compute the scale
                    scale = (float) (zoom / 100f);

                    // align our viewport
                    alignViewPort(e.getPoint());

                    // invalidate and repaint to update components
                    imagePanel.revalidate();
                    scrollPane.repaint();
                }
            }
        });

        // display our frame
        frame.setVisible(true);
    }

    // the main method
    public static void main(String[] args) {
        new ZoomScrollPanel();
    }
}
Florance answered 28/5, 2017 at 21:52 Comment(0)
C
1

Some time ago I was facing the same issue. I had some scalable/zoomable content (SWT widgets) stored in Viewport in JScrollPane and some features implemented to enable panning and zooming the content. I didn't look into your code if it's basically the same, but the issue that I was observing was completely the same. When zooming outside from the right/bottom side, sometimes, the view position jumped a little bit into the center (from my point-of-view that definitely points to a scale factor). Using doubled "setViewPosition" somehow enhanced the behavior but still not usable.

After some investigation, I've found out that the issue on my side was between the moment when I changed the scale factor of the content inside the scroll panel and the moment when view position was set in scroll panel. The thing is that scroll panel doesn't know about the content size updates until layout is done. So basically, it's updating the position based on old content size, extent size and view position.

So, at my side, this helped a lot.

// updating scroll panel content scale goes here

viewport.doLayout();

// setting view position in viewport goes here

Checking method BasicScrollPaneUI#syncScrollPaneWithViewport() was very useful on my side.

Cocci answered 9/12, 2020 at 19:34 Comment(0)
L
0

very useful example, excellent zoom at mouse pointer, here is the same code slightly modified to include mouse panning: original code added taken from --> Scroll JScrollPane by dragging mouse (Java swing)

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * Zoom-able scroll panel
 */

// https://mcmap.net/q/1552510/-zoomable-jscrollpane-setviewposition-fails-to-update

public class ZoomPanScrollPanel {

    // the size of our image
    private final static int IMAGE_SIZE = 1600;

    // create an image to display
    private BufferedImage getImage() {
        BufferedImage image = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();
        // draw the small pixel first
        Random rand = new Random();
        for (int x = 0; x < IMAGE_SIZE; x += 10) {
            for (int y = 0; y < IMAGE_SIZE; y += 10) {
                g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), rand.nextInt(255)));
                g.fillRect(x, y, 10, 10);
            }
        }
        // draw the larger transparent pixel second
        for (int x = 0; x < IMAGE_SIZE; x += 100) {
            for (int y = 0; y < IMAGE_SIZE; y += 100) {
                g.setColor(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255), 180));
                g.fillRect(x, y, 100, 100);
            }
        }
        return image;
    }

    // the image panel that resizes according to zoom level
    private class ImagePanel extends JPanel {
        private final BufferedImage image = getImage();

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D)g.create();
            g2.scale(scale, scale);
            g2.drawImage(image, 0, 0, null);
            g2.dispose();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension((int)Math.round(IMAGE_SIZE * scale), (int)Math.round(IMAGE_SIZE * scale));
        }
    }

    // the current zoom level (100 means the image is shown in original size)
    private double zoom = 100;
    // the current scale (scale = zoom/100)
    private double scale = 1;

    // the last seen scale
    private double lastScale = 1;

    // true if currently executing setViewPosition
    private boolean blocked = false;

    public void alignViewPort(Point mousePosition) {
        // if the scale didn't change there is nothing we should do
        if (scale != lastScale) {
            // compute the factor by that the image zoom has changed
            double scaleChange = scale / lastScale;

            // compute the scaled mouse position
            Point scaledMousePosition = new Point(
                    (int)Math.round(mousePosition.x * scaleChange),
                    (int)Math.round(mousePosition.y * scaleChange)
            );

            // retrieve the current viewport position
            Point viewportPosition = scrollPane.getViewport().getViewPosition();

            // compute the new viewport position
            Point newViewportPosition = new Point(
                    viewportPosition.x + scaledMousePosition.x - mousePosition.x,
                    viewportPosition.y + scaledMousePosition.y - mousePosition.y
            );

            // update the viewport position
            blocked = true;
            scrollPane.getViewport().setViewPosition(newViewportPosition);
            blocked = false;

            // remember the last scale
            lastScale = scale;
        }
    }

    // reference to the scroll pane container
    private final JScrollPane scrollPane;

    // constructor
    public ZoomPanScrollPanel() {
        // initialize the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(600, 600);

        // initialize the components
        final ImagePanel imagePanel = new ImagePanel();
        final JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new GridBagLayout());
        centerPanel.add(imagePanel);
        scrollPane = new JScrollPane();

        scrollPane.setViewport(new JViewport() {
            private boolean inCall = false;
            @Override
            public void setViewPosition(Point pos) {
                if (!inCall || !blocked) {
                    inCall = true;
                    super.setViewPosition(pos);
                    inCall = false;
                }
            }
        });

        scrollPane.getViewport().add(centerPanel);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        frame.add(scrollPane);

        // add mouse wheel listener
        imagePanel.addMouseWheelListener(new MouseAdapter() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                super.mouseWheelMoved(e);
                // check the rotation of the mousewheel
                int rotation = e.getWheelRotation();
                boolean zoomed = false;
                if (rotation > 0) {
                    // only zoom out until no scrollbars are visible
                    if (scrollPane.getHeight() < imagePanel.getPreferredSize().getHeight() ||
                            scrollPane.getWidth() < imagePanel.getPreferredSize().getWidth()) {
                        zoom = zoom / 1.3;
                        zoomed = true;
                    }
                } else {
                    // zoom in until maximum zoom size is reached
                    double newCurrentZoom = zoom * 1.3;
                    if (newCurrentZoom < 1000) { // 1000 ~ 10 times zoom
                        zoom = newCurrentZoom;
                        zoomed = true;
                    }
                }
                // check if a zoom happened
                if (zoomed) {
                    // compute the scale
                    scale = (float) (zoom / 100f);

                    // align our viewport
                    alignViewPort(e.getPoint());

                    // invalidate and repaint to update components
                    imagePanel.revalidate();
                    scrollPane.repaint();
                }
            }
        });
        
          //mouse panning
//original code: https://mcmap.net/q/915143/-scroll-jscrollpane-by-dragging-mouse-java-swing

                MouseAdapter ma = new MouseAdapter() {

                    private Point origin;

                    @Override
                    public void mousePressed(MouseEvent e) {
                        origin = new Point(e.getPoint());
                    }

                    @Override
                    public void mouseReleased(MouseEvent e) {
                    }

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        if (origin != null) {
                            JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, imagePanel);
                            if (viewPort != null) {
                                int deltaX = origin.x - e.getX();
                                int deltaY = origin.y - e.getY();
                                
                                System.out.println("X pan = "+ deltaX);
                                System.out.println("Y pan = "+ deltaY);

                                Rectangle view = viewPort.getViewRect();
                                view.x += deltaX;
                                view.y += deltaY;

                                imagePanel.scrollRectToVisible(view);
                            }
                        }
                    }

                };

                imagePanel.addMouseListener(ma);
                imagePanel.addMouseMotionListener(ma);
                
                 imagePanel.setAutoscrolls(true);

        // display our frame
        frame.setVisible(true);
    }

    // the main method
    public static void main(String[] args) {
        new ZoomPanScrollPanel();
    }
}
Liquid answered 25/5, 2022 at 15:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.