// -*- mode:java; encoding:utf-8 -*-
// vim:set fileencoding=utf-8:
// @homepage@

package example;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import javax.swing.*;

public final class MainPanel extends JPanel {
  private transient ZoomAndPanHandler zoomAndPanHandler;
  private final ImageIcon icon;

  private MainPanel() {
    super(new BorderLayout());
    icon = new ImageIcon(getClass().getResource("CRW_3857_JFR.jpg"));
    setPreferredSize(new Dimension(320, 240));
  }

  @Override public void updateUI() {
    removeMouseListener(zoomAndPanHandler);
    removeMouseMotionListener(zoomAndPanHandler);
    removeMouseWheelListener(zoomAndPanHandler);
    super.updateUI();
    zoomAndPanHandler = new ZoomAndPanHandler();
    addMouseListener(zoomAndPanHandler);
    addMouseMotionListener(zoomAndPanHandler);
    addMouseWheelListener(zoomAndPanHandler);
  }

  @Override protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setTransform(zoomAndPanHandler.getCoordAndZoomTransform());
    icon.paintIcon(this, g2, 0, 0);
    g2.dispose();
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(MainPanel::createAndShowGui);
  }

  private static void createAndShowGui() {
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
      ex.printStackTrace();
      Toolkit.getDefaultToolkit().beep();
    }
    JFrame frame = new JFrame("@title@");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.getContentPane().add(new MainPanel());
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

class ZoomAndPanHandler extends MouseAdapter {
  private static final double ZOOM_MULTIPLICATION_FACTOR = 1.2;
  private static final int MIN_ZOOM = -10;
  private static final int MAX_ZOOM = 10;
  private static final int EXTENT = 1;
  private final BoundedRangeModel zoomRange = new DefaultBoundedRangeModel(0, EXTENT, MIN_ZOOM, MAX_ZOOM + EXTENT);
  private final AffineTransform coordAndZoomTransform = new AffineTransform();
  private final Point dragStartPoint = new Point();

  @Override public void mousePressed(MouseEvent e) {
    dragStartPoint.setLocation(e.getPoint());
  }

  @Override public void mouseDragged(MouseEvent e) {
    Point dragEndPoint = e.getPoint();
    Point dragStart = transformPoint(dragStartPoint);
    Point dragEnd = transformPoint(dragEndPoint);
    coordAndZoomTransform.translate(dragEnd.getX() - dragStart.getX(), dragEnd.getY() - dragStart.getY());
    dragStartPoint.setLocation(dragEndPoint);
    e.getComponent().repaint();
  }

  @Override public void mouseWheelMoved(MouseWheelEvent e) {
    int dir = e.getWheelRotation();
    int z = zoomRange.getValue();
    zoomRange.setValue(z + EXTENT * (dir > 0 ? -1 : 1));
    if (z == zoomRange.getValue()) {
      return;
    }
    Component c = e.getComponent();
    Rectangle r = c.getBounds();
    // Point p = e.getPoint();
    Point p = new Point(r.x + r.width / 2, r.y + r.height / 2);
    Point p1 = transformPoint(p);
    double scale = dir > 0 ? 1 / ZOOM_MULTIPLICATION_FACTOR : ZOOM_MULTIPLICATION_FACTOR;
    coordAndZoomTransform.scale(scale, scale);
    Point p2 = transformPoint(p);
    coordAndZoomTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
    c.repaint();
  }

  // https://community.oracle.com/thread/1263955
  // How to implement Zoom & Pan in Java using Graphics2D
  private Point transformPoint(Point p1) {
    AffineTransform inverse = coordAndZoomTransform;
    boolean hasInverse = coordAndZoomTransform.getDeterminant() != 0d;
    if (hasInverse) {
      try {
        inverse = coordAndZoomTransform.createInverse();
      } catch (NoninvertibleTransformException ex) {
        // should never happen
        assert false;
      }
    }
    Point p2 = new Point();
    inverse.transform(p1, p2);
    return p2;
  }

  public AffineTransform getCoordAndZoomTransform() {
    return coordAndZoomTransform;
  }
}

@@ -314,16 +314,46 @@
 eption;%0A
+import java.awt.geom.Point2D;%0A
 import j
@@ -2458,24 +2458,26 @@
  final Point
+2D
  dragStartPo
@@ -2495,16 +2495,16 @@
 oint();%0A
-
 %0A  @Over
@@ -2654,32 +2654,34 @@
 t e) %7B%0A    Point
+2D
  dragEndPoint = 
@@ -2699,24 +2699,26 @@
 );%0A    Point
+2D
  dragStart =
@@ -2759,16 +2759,18 @@
    Point
+2D
  dragEnd
@@ -3313,97 +3313,70 @@
 %0A   
- //
  Point
- p = e.getPoint();%0A    Point p = new Point(r.x + r.width / 2, r.y + r.height / 2
+2D p = new Point2D.Double(r.getCenterX(), r.getCenterY()
 );%0A 
@@ -3383,16 +3383,18 @@
    Point
+2D
  p1 = tr
@@ -3552,24 +3552,26 @@
 );%0A    Point
+2D
  p2 = transf
@@ -3810,16 +3810,18 @@
 te Point
+2D
  transfo
@@ -3833,16 +3833,18 @@
 nt(Point
+2D
  p1) %7B%0A 
@@ -4173,24 +4173,24 @@
     %7D%0A    %7D%0A
-
     Point p2
@@ -4186,16 +4186,18 @@
    Point
+2D
  p2 = ne

use new Point2D.Double(r.getCenterX(), r.getCenterY()) instead of new Point(...)