Zoom relative to mouse position

Discussion in 'Java' started by Amir Kouchekinia, May 20, 2008.

  1. Hi,

    I am trying to figure out how to zoom relative to the mouse pointer
    position. Below, please find my sample code.

    When I initially position the mouse pointer on one corner of the red
    rectangle and use the mouse wheel, I am able to zoom in and out as
    expected. As soon as I move the pointer position, let's say to another
    corner of the red rectangle, and try to zoom in or out, my position
    relative to the rectangle shifts around.

    I think I may be missing a transform or two. In another attempt, in the
    mouseWheelMoved method, I inverted tx from the previous iteration and
    transformed the mouse position p with the inverted transform before
    recalculating the new transform; but the behavior got more erratic.


    Any help is greatly appreciated. What am I missing here?

    Thanks.

    Here is my code:

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Path2D;
    import java.awt.geom.Rectangle2D;

    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;

    @SuppressWarnings("serial")
    public class ZoomDemo extends JPanel {

    AffineTransform tx = new AffineTransform();

    Rectangle2D.Double rect = new Rectangle2D.Double(100, 100, 20, 30);

    public ZoomDemo() {
    this.addMouseWheelListener(new ZoomHandler());
    }

    @Override
    public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2 = (Graphics2D) g;

    Path2D.Double path;
    g2.setColor(Color.RED);
    path = new Path2D.Double(rect, tx);
    g2.draw(path);
    }

    private class ZoomHandler implements MouseWheelListener {

    Point oldPoint = null;

    double scale = 1.0;

    public void mouseWheelMoved(MouseWheelEvent e) {
    if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

    scale += (.1 * e.getWheelRotation());
    scale = Math.max(0.1, scale);
    Point p = e.getPoint();

    tx = AffineTransform.getTranslateInstance(p.getX(), p.getY());
    tx.scale(scale, scale);
    tx.translate(-p.getX(), -p.getY());

    ZoomDemo.this.revalidate();
    ZoomDemo.this.repaint();
    }
    }
    }

    public static void main(String[] args) {

    JFrame f = new JFrame("ZoomDemo");
    ZoomDemo zoomDemo = new ZoomDemo();
    JScrollPane sp = new JScrollPane(zoomDemo);
    f.getContentPane().add(sp);
    f.setSize(500, 500);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
    }
    }
    Amir Kouchekinia, May 20, 2008
    #1
    1. Advertising

  2. In article <IOqYj.2253$>,
    Amir Kouchekinia <amir_nospam@pyrus_nospam.us> wrote:

    > Hi,
    >
    > I am trying to figure out how to zoom relative to the mouse pointer
    > position. Below, please find my sample code.
    >
    > When I initially position the mouse pointer on one corner of the red
    > rectangle and use the mouse wheel, I am able to zoom in and out as
    > expected. As soon as I move the pointer position, let's say to another
    > corner of the red rectangle, and try to zoom in or out, my position
    > relative to the rectangle shifts around.
    >
    > I think I may be missing a transform or two. In another attempt, in the
    > mouseWheelMoved method, I inverted tx from the previous iteration and
    > transformed the mouse position p with the inverted transform before
    > recalculating the new transform; but the behavior got more erratic.
    >
    >
    > Any help is greatly appreciated. What am I missing here?


    You want to transform a Shape's coordinates, but the normal
    concatenation of transformations is arranged for a different goal.
    Recalling that matrix multiplication is _not_ commutative, if you want
    to scale then translate, you have to apply then in reverse order. See
    concatenate() and preConcatenate(), as well as the scaled arrow thread
    in this group.

    Is this the effect you wanted? [Path2D replaced by Shape for 5.0 users.]

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.Shape;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Rectangle2D;
    import javax.swing.JFrame;
    import javax.swing.JPanel;

    @SuppressWarnings("serial")
    public class ZoomDemo extends JPanel {

    AffineTransform tx = new AffineTransform();

    Rectangle2D.Double rect = new Rectangle2D.Double(-15, -30, 30, 60);

    public ZoomDemo() {
    this.addMouseWheelListener(new ZoomHandler());
    }

    @Override
    public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.RED);
    Shape shape = tx.createTransformedShape(rect);
    g2.draw(shape);
    }

    private class ZoomHandler implements MouseWheelListener {

    double scale = 1.0;

    public void mouseWheelMoved(MouseWheelEvent e) {
    if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

    scale += (.1 * e.getWheelRotation());
    scale = Math.max(0.1, scale);
    Point p = e.getPoint();

    tx.setToIdentity();
    tx.translate(p.getX(), p.getY());
    tx.scale(scale, scale);

    ZoomDemo.this.revalidate();
    ZoomDemo.this.repaint();
    }
    }
    }

    public static void main(String[] args) {

    JFrame f = new JFrame("ZoomDemo");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    ZoomDemo zoomDemo = new ZoomDemo();
    f.getContentPane().add(zoomDemo);
    f.setSize(500, 500);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
    }
    }

    John
    --
    John B. Matthews
    trashgod at gmail dot com
    home dot woh dot rr dot com slash jbmatthews
    John B. Matthews, May 20, 2008
    #2
    1. Advertising

  3. Hi John,

    Thank you very much for your reply.

    I have tried your code, and the behavior is not what I am looking for.

    The red rectangle in my code was located at 100,100 point in the initial
    view (Scale 1.0, No Translate). I am not sure why you have centered the
    rectangle to the 0,0 point. Also, where ever I try to zoom, the red
    rectangle ends up being centered around that point.

    This is the behavior I want to see: When I place the mouse pointer over
    the lower-left corner of the rectangle and zoom, I want the lower-left
    corner of the rectangle to remain at the original mouse pointer location
    relative to the screen. Now if I move the mouse pointer to the
    upper-left corner of the rectangle and zoom, I want the upper-left
    corner of the rectangle to remain at the new mouse pointer location
    relative to the screen. If there where other shapes on the canvas, I'd
    expect similar behavior when the mouse pointer is located over one of
    those other shapes.

    I have found an example of this behavior on my Mac OSX 10.5 desktop:
    If I hold the 'control' key and use the mouse wheel to zoom in, the
    point in the object displayed under the mouse pointer stays under the
    mouse pointer relative to the screen.

    I have looked at the Arrow thread in this group, but have not been able
    to extract the answer I am looking for.

    Thanks again.

    Amir



    John B. Matthews wrote:
    > In article <IOqYj.2253$>,
    > Amir Kouchekinia <amir_nospam@pyrus_nospam.us> wrote:
    >
    >> Hi,
    >>
    >> I am trying to figure out how to zoom relative to the mouse pointer
    >> position. Below, please find my sample code.
    >>
    >> When I initially position the mouse pointer on one corner of the red
    >> rectangle and use the mouse wheel, I am able to zoom in and out as
    >> expected. As soon as I move the pointer position, let's say to another
    >> corner of the red rectangle, and try to zoom in or out, my position
    >> relative to the rectangle shifts around.
    >>
    >> I think I may be missing a transform or two. In another attempt, in the
    >> mouseWheelMoved method, I inverted tx from the previous iteration and
    >> transformed the mouse position p with the inverted transform before
    >> recalculating the new transform; but the behavior got more erratic.
    >>
    >>
    >> Any help is greatly appreciated. What am I missing here?

    >
    > You want to transform a Shape's coordinates, but the normal
    > concatenation of transformations is arranged for a different goal.
    > Recalling that matrix multiplication is _not_ commutative, if you want
    > to scale then translate, you have to apply then in reverse order. See
    > concatenate() and preConcatenate(), as well as the scaled arrow thread
    > in this group.
    >
    > Is this the effect you wanted? [Path2D replaced by Shape for 5.0 users.]
    >
    > import java.awt.Color;
    > import java.awt.Graphics;
    > import java.awt.Graphics2D;
    > import java.awt.Point;
    > import java.awt.Shape;
    > import java.awt.event.MouseWheelEvent;
    > import java.awt.event.MouseWheelListener;
    > import java.awt.geom.AffineTransform;
    > import java.awt.geom.Rectangle2D;
    > import javax.swing.JFrame;
    > import javax.swing.JPanel;
    >
    > @SuppressWarnings("serial")
    > public class ZoomDemo extends JPanel {
    >
    > AffineTransform tx = new AffineTransform();
    >
    > Rectangle2D.Double rect = new Rectangle2D.Double(-15, -30, 30, 60);
    >
    > public ZoomDemo() {
    > this.addMouseWheelListener(new ZoomHandler());
    > }
    >
    > @Override
    > public void paint(Graphics g) {
    > super.paint(g);
    > Graphics2D g2 = (Graphics2D) g;
    > g2.setColor(Color.RED);
    > Shape shape = tx.createTransformedShape(rect);
    > g2.draw(shape);
    > }
    >
    > private class ZoomHandler implements MouseWheelListener {
    >
    > double scale = 1.0;
    >
    > public void mouseWheelMoved(MouseWheelEvent e) {
    > if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
    >
    > scale += (.1 * e.getWheelRotation());
    > scale = Math.max(0.1, scale);
    > Point p = e.getPoint();
    >
    > tx.setToIdentity();
    > tx.translate(p.getX(), p.getY());
    > tx.scale(scale, scale);
    >
    > ZoomDemo.this.revalidate();
    > ZoomDemo.this.repaint();
    > }
    > }
    > }
    >
    > public static void main(String[] args) {
    >
    > JFrame f = new JFrame("ZoomDemo");
    > f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    > ZoomDemo zoomDemo = new ZoomDemo();
    > f.getContentPane().add(zoomDemo);
    > f.setSize(500, 500);
    > f.setLocationRelativeTo(null);
    > f.setVisible(true);
    > }
    > }
    >
    > John
    Amir Kouchekinia, May 20, 2008
    #3
  4. In article <J4BYj.9256$>,
    Amir Kouchekinia <amir_nospam@pyrus_nospam.us> wrote:

    > Hi John,
    >
    > Thank you very much for your reply.
    >
    > I have tried your code, and the behavior is not what I am looking for.
    >
    > The red rectangle in my code was located at 100,100 point in the initial
    > view (Scale 1.0, No Translate). I am not sure why you have centered the
    > rectangle to the 0,0 point. Also, where ever I try to zoom, the red
    > rectangle ends up being centered around that point.


    Correct. The rectangle starts out centered on the origin of a Cartesian
    plane (user space). As the graphics environment's default transformation
    matrix is unchanged, the rectangle is rendered upright on the screen
    (device space) and centered on the origin (left, upper corner).
    Translating it to the current mouse point leaves it centered on the
    mouse point, scaled according to the mouse wheel. Try altering the
    original rectangle from (-15, -30, 30, 60) to (-15, -5, 30, 60) to see
    how the rectangle remains upright (due to the g2's default transform)
    and how it is positioned (due to the tx transform).

    > This is the behavior I want to see: When I place the mouse pointer over
    > the lower-left corner of the rectangle and zoom, I want the lower-left
    > corner of the rectangle to remain at the original mouse pointer location
    > relative to the screen. Now if I move the mouse pointer to the
    > upper-left corner of the rectangle and zoom, I want the upper-left
    > corner of the rectangle to remain at the new mouse pointer location
    > relative to the screen. If there where other shapes on the canvas, I'd
    > expect similar behavior when the mouse pointer is located over one of
    > those other shapes.


    Only the shape under the mouse changes, and the other shapes remain
    unaffected? You'll have to implement MouseMotionListener, determine
    which object includes the mouse point, and scale/translate the
    individual rectangle relative to the current mouse position.

    > I have found an example of this behavior on my Mac OSX 10.5 desktop:
    > If I hold the 'control' key and use the mouse wheel to zoom in, the
    > point in the object displayed under the mouse pointer stays under the
    > mouse pointer relative to the screen.


    In this case, the entire Desktop zooms (with the wheel) and pans (with
    the mouse). Let's start with the zoom. Such an effect is most easily
    achieved by concatenating transformations with the g2's default
    transform. Of course, this zooms the entire view, not just a single
    object as you suggest above. Compare the order of concatenation in
    following example to the previous one. The rotate is superfluous, but
    interesting to alter.

    Panning is left as an exercise with MouseMotionListener. Hint: Instead
    of translating to the center of the window (as shown below), you'd
    translate in a direction determined by the difference between successive
    mouse positions.

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.Shape;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Rectangle2D;
    import javax.swing.JFrame;
    import javax.swing.JPanel;

    @SuppressWarnings("serial")
    public class ViewZoom extends JPanel {

    private static final int SIZE = 500;
    private double scale = 2d;
    Rectangle2D.Double rect1 = new Rectangle2D.Double(-25, -35, 20, 30);
    Rectangle2D.Double rect2 = new Rectangle2D.Double(5, 5, 30, 50);
    Rectangle2D.Double rect3 = new Rectangle2D.Double(-15, -15, 30, 30);

    public ViewZoom() {
    this.addMouseWheelListener(new ZoomHandler());
    }

    @Override
    public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.BLACK);
    g2.drawLine(0, SIZE / 2, SIZE, SIZE / 2);
    g2.drawLine(SIZE / 2, 0, SIZE / 2, SIZE);
    AffineTransform saveAT = g2.getTransform();
    g2.translate(SIZE / 2, SIZE / 2);
    g2.rotate(Math.PI / 4);
    g2.scale(scale, scale);
    g2.setColor(Color.RED);
    g2.draw(rect1);
    g2.draw(rect2);
    g2.setColor(Color.BLUE);
    g2.draw(rect3);
    g2.setTransform(saveAT);
    }

    private class ZoomHandler implements MouseWheelListener {

    public void mouseWheelMoved(MouseWheelEvent e) {
    if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

    scale += (.1 * e.getWheelRotation());
    scale = Math.max(0.1, scale);
    scale = Math.min(scale, 10);

    ViewZoom.this.revalidate();
    ViewZoom.this.repaint();
    }
    }
    }

    public static void main(String[] args) {

    JFrame f = new JFrame("ViewZoom");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    ViewZoom viewZoom = new ViewZoom();
    f.getContentPane().add(viewZoom);
    f.setSize(SIZE, SIZE);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
    }
    }

    John
    --
    John B. Matthews
    trashgod at gmail dot com
    home dot woh dot rr dot com slash jbmatthews
    John B. Matthews, May 20, 2008
    #4
  5. In article <>,
    "John B. Matthews" <> wrote:
    [...]
    > Correct. The rectangle starts out centered on the origin of a Cartesian
    > plane (user space). As the graphics environment's default transformation
    > matrix is unchanged, the rectangle is rendered upright on the screen
    > (device space) and centered on the origin (left, upper corner).
    > Translating it to the current mouse point leaves it centered on the
    > mouse point, scaled according to the mouse wheel. Try altering the
    > original rectangle from (-15, -30, 30, 60) to (-15, -5, 30, 60) to see
    > how the rectangle remains upright (due to the g2's default transform)
    > and how it is positioned (due to the tx transform).

    [...]

    Oops, not Cartesian. Rather, it's the vertical inverse, with ordinates
    increasing top-to-bottom.

    John
    --
    John B. Matthews
    trashgod at gmail dot com
    home dot woh dot rr dot com slash jbmatthews
    John B. Matthews, May 20, 2008
    #5
  6. I've finally figured this out. Here's my code if you are interested:

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.NoninvertibleTransformException;
    import java.awt.geom.Point2D;
    import java.awt.geom.Rectangle2D;

    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;

    @SuppressWarnings("serial")
    public class ZoomDemo extends JPanel implements Runnable {

    AffineTransform tx = new AffineTransform();

    Rectangle2D.Double rect1 = new Rectangle2D.Double(100, 100, 30, 60);
    Rectangle2D.Double rect2 = new Rectangle2D.Double(150, 250, 60, 40);

    public ZoomDemo() {
    this.addMouseWheelListener(new ZoomHandler());
    }

    @Override
    public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.RED);
    g2.draw(tx.createTransformedShape(rect1));
    g2.setColor(Color.BLUE);
    g2.draw(tx.createTransformedShape(rect2));
    }

    private class ZoomHandler implements MouseWheelListener {

    double scale = 1.0;

    public void mouseWheelMoved(MouseWheelEvent e) {
    if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

    Point2D p1 = e.getPoint();
    Point2D p2 = null;
    try {
    p2 = tx.inverseTransform(p1, null);
    } catch (NoninvertibleTransformException ex) {
    // should not get here
    ex.printStackTrace();
    return;
    }

    scale -= (0.1 * e.getWheelRotation());
    scale = Math.max(0.1, scale);

    tx.setToIdentity();
    tx.translate(p1.getX(), p1.getY());
    tx.scale(scale, scale);
    tx.translate(-p2.getX(), -p2.getY());

    ZoomDemo.this.revalidate();
    ZoomDemo.this.repaint();
    }
    }
    }

    public void run() {
    JFrame f = new JFrame("Zoom Demo");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.getContentPane().add(this);
    f.setSize(600, 600);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
    }

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












    Amir Kouchekinia wrote:
    > Hi,
    >
    > I am trying to figure out how to zoom relative to the mouse pointer
    > position. Below, please find my sample code.
    >
    > When I initially position the mouse pointer on one corner of the red
    > rectangle and use the mouse wheel, I am able to zoom in and out as
    > expected. As soon as I move the pointer position, let's say to another
    > corner of the red rectangle, and try to zoom in or out, my position
    > relative to the rectangle shifts around.
    >
    > I think I may be missing a transform or two. In another attempt, in the
    > mouseWheelMoved method, I inverted tx from the previous iteration and
    > transformed the mouse position p with the inverted transform before
    > recalculating the new transform; but the behavior got more erratic.
    >
    >
    > Any help is greatly appreciated. What am I missing here?
    >
    > Thanks.
    >
    > Here is my code:
    >
    > import java.awt.Color;
    > import java.awt.Graphics;
    > import java.awt.Graphics2D;
    > import java.awt.Point;
    > import java.awt.event.MouseWheelEvent;
    > import java.awt.event.MouseWheelListener;
    > import java.awt.geom.AffineTransform;
    > import java.awt.geom.Path2D;
    > import java.awt.geom.Rectangle2D;
    >
    > import javax.swing.JFrame;
    > import javax.swing.JPanel;
    > import javax.swing.JScrollPane;
    >
    > @SuppressWarnings("serial")
    > public class ZoomDemo extends JPanel {
    >
    > AffineTransform tx = new AffineTransform();
    >
    > Rectangle2D.Double rect = new Rectangle2D.Double(100, 100, 20, 30);
    >
    > public ZoomDemo() {
    > this.addMouseWheelListener(new ZoomHandler());
    > }
    >
    > @Override
    > public void paint(Graphics g) {
    > super.paint(g);
    > Graphics2D g2 = (Graphics2D) g;
    >
    > Path2D.Double path;
    > g2.setColor(Color.RED);
    > path = new Path2D.Double(rect, tx);
    > g2.draw(path);
    > }
    >
    > private class ZoomHandler implements MouseWheelListener {
    >
    > Point oldPoint = null;
    >
    > double scale = 1.0;
    >
    > public void mouseWheelMoved(MouseWheelEvent e) {
    > if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
    >
    > scale += (.1 * e.getWheelRotation());
    > scale = Math.max(0.1, scale);
    > Point p = e.getPoint();
    >
    > tx = AffineTransform.getTranslateInstance(p.getX(), p.getY());
    > tx.scale(scale, scale);
    > tx.translate(-p.getX(), -p.getY());
    >
    > ZoomDemo.this.revalidate();
    > ZoomDemo.this.repaint();
    > }
    > }
    > }
    >
    > public static void main(String[] args) {
    >
    > JFrame f = new JFrame("ZoomDemo");
    > ZoomDemo zoomDemo = new ZoomDemo();
    > JScrollPane sp = new JScrollPane(zoomDemo);
    > f.getContentPane().add(sp);
    > f.setSize(500, 500);
    > f.setLocationRelativeTo(null);
    > f.setVisible(true);
    > }
    > }
    Amir Kouchekinia, May 22, 2008
    #6
  7. In article <Vl7Zj.104$>,
    Amir Kouchekinia <amir_nospam@pyrus_nospam.us> wrote:

    > I've finally figured this out. Here's my code if you are interested:
    > [...]
    > Point2D p1 = e.getPoint();
    > Point2D p2 = null;
    > try {
    > p2 = tx.inverseTransform(p1, null);
    > } catch (NoninvertibleTransformException ex) {
    > // should not get here
    > ex.printStackTrace();
    > return;
    > }
    > [...]


    Very nice! Not _exactly_ the Desktop, but an interesting effect.

    John
    --
    John B. Matthews
    trashgod at gmail dot com
    home dot woh dot rr dot com slash jbmatthews
    John B. Matthews, May 23, 2008
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    1
    Views:
    728
    Keith James
    Feb 14, 2005
  2. Replies:
    1
    Views:
    528
    jfalt
    Feb 15, 2005
  3. Replies:
    0
    Views:
    417
  4. Replies:
    0
    Views:
    327
  5. DJ WIce

    Zoom page -> Mouse position wrong?

    DJ WIce, Jan 21, 2004, in forum: Javascript
    Replies:
    1
    Views:
    132
    DJ WIce
    Jan 22, 2004
Loading...

Share This Page