zoom using mouse and graphics

I draw in my JComponent some curves, etc .. with Graphics G ( not 2D ).

Now I want to use the scroll wheel of my mouse to zoom in and out.

Any tracks ?

I heard talk about a BuferredImage ?


There are a few considerations you need to take into account...

The end result will depend on what you want to achieve. If you are drawing curves using the Graphics2D API, it might be simpler to simply scale the coordinates each time the component is rendered. You will need to make sure that any changes in the scale are reflected in the preferred size of the component itself.

You could also render the "default" output to a BufferedImage and simply use an AffineTransform to change the scaling the is used to render the result, for example.

This simple uses a BufferedImage and loads a picture from disk, but the basic concept is the same.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ZoomPane {

    public static void main(String[] args) {
        new ZoomPane();
    }

    public ZoomPane() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new TestPane()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage img;
        private float scale = 1;

        public TestPane() {
            try {
                img = ImageIO.read(new File("/path/to/image"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            addMouseWheelListener(new MouseAdapter() {

                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    double delta = 0.05f * e.getPreciseWheelRotation();
                    scale += delta;
                    revalidate();
                    repaint();
                }

            });
        }

        @Override
        public Dimension getPreferredSize() {            
            Dimension size = new Dimension(200, 200);
            if (img != null) {            
                size.width = Math.round(img.getWidth() * scale);
                size.height = Math.round(img.getHeight() * scale);                
            }        
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (img != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                AffineTransform at = new AffineTransform();
                at.scale(scale, scale);
                g2d.drawImage(img, at, this);
                g2d.dispose();
            }
        }
    }

}

You could also scale the Graphics context passed to your paintComponent method directly.

The important thing here is to remember to reset the AffineTransform after you have completed, otherwise it will be passed to other components when they are rendered, which won't generate the expected output...

This example basically creates a copy of the Graphics context which we can manipulate and dispose of without effecting the original, making it simpler to mess with

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ZoomPane {

    public static void main(String[] args) {
        new ZoomPane();
    }

    public ZoomPane() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new TestPane()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private float scale = 1;

        public TestPane() {
            addMouseWheelListener(new MouseAdapter() {

                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    double delta = 0.05f * e.getPreciseWheelRotation();
                    scale += delta;
                    revalidate();
                    repaint();
                }

            });
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = new Dimension(200, 200);
            size.width = Math.round(size.width * scale);
            size.height = Math.round(size.height * scale);
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            AffineTransform at = new AffineTransform();
            at.scale(scale, scale);
            g2d.setTransform(at);

            g2d.setColor(Color.RED);

            // This is for demonstration purposes only
            // I prefer to use getWidth and getHeight
            int width = 200;
            int height = 200;

            Path2D.Float path = new Path2D.Float();
            int seg = width / 3;
            path.moveTo(0, height / 2);
            path.curveTo(0, 0, seg, 0, seg, height / 2);
            path.curveTo(
                    seg, height, 
                    seg * 2, height, 
                    seg * 2, height / 2);
            path.curveTo(
                    seg * 2, 0, 
                    seg * 3, 0, 
                    seg * 3, height / 2);

            g2d.draw(path);


            g2d.dispose();
        }
    }
}

Take a look at Transforming Shapes, Text and Images for more details


Try JFreeChart; the setMouseWheelEnabled() method, used to control zooming in ChartPanel, is illustrated in examples cited here.