Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zooming graphics based on current mouse position

I'm trying to zoom a drawing based on the current position of the mouse. Right now my onMouseWheel method looks like this (based on this StackOverflow answer):

    private void onMouseWheel(object sender, MouseEventArgs e)
    {
        if (e.Delta > 0)
        {
            _scale *= 1.25f;
            _translateY = e.Y - 1.25f * (e.Y - _translateY);
            _translateX = e.X - 1.25f * (e.X - _translateX);
        }
        else
        {
            _scale /= 1.25f;
            _translateY = e.Y - 0.8f * (e.Y - _translateY);
            _translateX = e.X - 0.8f * (e.X - _translateX);
        }
        this.Invalidate();
    }

_scale, _translateX, and _translateY are member variables.

I'm scaling the graphics, translating it, and then drawing the lines like this:

    protected override void OnPaint(PaintEventArgs e)
    {
        g.ScaleTransform(_scale, _scale);
        g.TranslateTransform(_translateX, _translateY);
        //draw lines here
    }

This video shows what happens when I try to zoom in and the zoom out on a certain point. What am I doing wrong?

This is what the code looks like in a sample panel class for testing purposes:

class Display : Panel
{
    public Display()
    {
        this.MouseWheel += new MouseEventHandler(this.onMouseWheel);
    }

    private void onMouseWheel(object sender, MouseEventArgs e)
    {
        if (e.Delta > 0)
        {
            _scale *= 1.25f;
            _translateY = e.Y - 1.25f * (e.Y - _translateY);
            _translateX = e.X - 1.25f * (e.X - _translateX);
        }
        else
        {
            _scale /= 1.25f;
            _translateY = e.Y - 0.8f * (e.Y - _translateY);
            _translateX = e.X - 0.8f * (e.X - _translateX);
        }
        this.Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        g.ScaleTransform(_scale, _scale);
        g.TranslateTransform(_translateX, _translateY);

        Pen pen = new Pen(Color.Red);
        g.FillEllipse(pen.Brush, 50, 50, 10, 10);
    }
}
like image 556
Nathan Bierema Avatar asked May 16 '16 20:05

Nathan Bierema


People also ask

How do I zoom in using mouse position on a picture?

Image zooming Next, press left mouse button over the image and drag the mouse Up to zoom in or Down to zoom out. By default, the zooming tool is associated with the right mouse button.

How do you zoom using a mouse?

The wheel also provides a quick alternative to the Zoom control. (Interesting, there's no keyboard shortcut for accessing the Zoom control.) To zoom in and out using the mouse, hold down the [Ctrl] key while you turn the mouse wheel. Each click, up or down, increases or decreases the zoom factor by 10%.

How do I get the current position of my mouse?

To determine the mouse's current position, we use the statement, pyautogui. position(). This function returns a tuple of the position of the mouse's cursor.


1 Answers

Too lazy to make the equations right (and most likely would made similar mistake as you... I do not know if it is just me but exactly this easy stuff I cant handle and drives me mad). Instead I am dealing with this kind of tasks as follows (it is much safer from mistakes):

  1. create transform functions between screen and world coordinates

    So your mouse position is in screen coordinates and rendered stuff is in world coordinates. As this is only 2D then it is easy. make function that converts between these two. Your world to screen transform (if I am not overlooking something) is this:

    g.ScaleTransform(_scale, _scale);
    g.TranslateTransform(_translateX, _translateY);
    

    so:

    screen_x=(world_x*_scale)+_translateX;
    screen_y=(world_y*_scale)+_translateY;
    

    So the reverse:

    world_x=(screen_x-_translateX)/_scale;
    world_y=(screen_y-_translateY)/_scale;
    
  2. change of zoom/scale

    The idea is that after zoom/scale change the mouse position should stay the same in world coordinates as before. so remember world coordinates of mouse before change. Then compute from it the screen position after change and the difference put into translation.

Here simple C++ example:

double x0=0.0,y0=0.0,zoom=1.0,mx,my;
//---------------------------------------------------------------------------
void scr2obj(double &ox,double &oy,double sx,double sy)
    {
    ox=(sx-x0)/zoom;
    oy=(sy-y0)/zoom;
    }
//---------------------------------------------------------------------------
void obj2scr(double &sx,double &sy,double ox,double oy)
    {
    sx=x0+(ox*zoom);
    sy=y0+(oy*zoom);
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheelDown(TObject *Sender, TShiftState Shift,TPoint &MousePos, bool &Handled)
    {
    double mx0,my0;
    scr2obj(mx0,my0,mx,my);
    zoom/=1.25; // zoom out
    obj2scr(mx0,my0,mx0,my0);
    x0+=mx-mx0;
    y0+=my-my0;
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheelUp(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled)
    {
    double mx0,my0;
    scr2obj(mx0,my0,mx,my);
    zoom*=1.25; // zoom in
    obj2scr(mx0,my0,mx0,my0);
    x0+=mx-mx0;
    y0+=my-my0;
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X,int Y)
    {
    mx=X; my=Y;
    }
//---------------------------------------------------------------------------

the mx,my is actual mouse position in screen coordinates, the x0,y0 is the translation and zoom is the scale.

And here captured GIF animation of this:

example

[edit1] It looks like your gfx objects use transponed matrices

That means the order of transformations is reversed so the equations change a bit... Here your case example in C++:

void scr2obj(double &ox,double &oy,double sx,double sy) 
 { 
 // ox=(sx-x0)/zoom; 
 // oy=(sy-y0)/zoom; 
 ox=(sx/zoom)-x0; 
 oy=(sy/zoom)-y0; 
 } 
//--------------------------------------------------------------------------- 
void obj2scr(double &sx,double &sy,double ox,double oy) 
 { 
 // sx=x0+(ox*zoom); 
 // sy=y0+(oy*zoom); 
 sx=(x0+ox)*zoom; 
 sy=(y0+oy)*zoom; 
 } 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::FormMouseWheelDown(TObject *Sender, TShiftState Shift,TPoint &MousePos, bool &Handled) 
 { 
 double mx0,my0; 
 scr2obj(mx0,my0,mx,my); 
 zoom/=1.25; // zoom out 
 obj2scr(mx0,my0,mx0,my0); 
 // x0+=mx-mx0; 
 // y0+=my-my0; 
 x0+=(mx-mx0)/zoom; 
 y0+=(my-my0)/zoom; 
 _redraw=true; 
 } 
//--------------------------------------------------------------------------- 
void __fastcall TForm1::FormMouseWheelUp(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled) 
 { 
 double mx0,my0; 
 scr2obj(mx0,my0,mx,my); 
 zoom*=1.25; // zoom in 
 obj2scr(mx0,my0,mx0,my0); 
 // x0+=mx-mx0; 
 // y0+=my-my0; 
 x0+=(mx-mx0)/zoom; 
 y0+=(my-my0)/zoom; 
 _redraw=true; 
 } 
//---------------------------------------------------------------------------
like image 59
Spektre Avatar answered Oct 28 '22 17:10

Spektre