6

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);
    }
}
Community
  • 1
  • 1
Nathan Bierema
  • 1,813
  • 2
  • 14
  • 24
  • Can you upload the code of your sample application? I think it has to do with your scale, is it working if you use `_scale += 0.25f` instead of the multiplication. Though it's just a guess, because I don't have a working example right now. – dwonisch May 16 '16 at 20:15
  • I added a sample panel class that you can just add to a form. The addition does not work. That would make the zoom go negative if I zoom out too much. It has to do with the translation. The scaling works fine. – Nathan Bierema May 16 '16 at 20:25

1 Answers1

3

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; 
 } 
//---------------------------------------------------------------------------
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • It does the same thing when I use your code. Have you tested this? If so, can you provide the code from the entire class? – Nathan Bierema May 17 '16 at 13:13
  • Your equation under #2 seems to do the same thing as my equation. The _scale ends up being cancelled and you end up multiplying by 1.25. Could it be that the Graphics coordinates do not have the same origin as the mouse coordinates? – Nathan Bierema May 17 '16 at 13:48
  • @NathanBierema Yes I made a mistake while writing it ... (the same I did many times before..) was in a hurry in the morning. see have added functional source code (from VCL but you can simply port it to your code) with what I have in mind. If even this does not work than you got wrong mouse coordinates (not relative to your App but to desktop instead) – Spektre May 17 '16 at 14:50
  • @NathanBierema Added better captured GIF so you see that this really works I use it for ages ... – Spektre May 17 '16 at 15:08
  • Okay, it still doesn't work with that code. My mouse coordinates seem to be approximately correct because if I draw something at (100, 100) and click on it, e.X and e.Y are within a pixel of (100, 100) (my mouse might be a couple of pixels off). How do I figure out the mouse coordinates relative to the Graphics object? – Nathan Bierema May 17 '16 at 15:12
  • I'm pretty sure my mouse coordinates are spot on using various tests. Could it be anything else? – Nathan Bierema May 17 '16 at 15:19
  • @NathanBierema in your environment I do not know, you can substract main window coordinates or use winapi but both are not 100% ok. I use on mouse move event to capture relative coordinates of mouse (those in on mouse wheel are absolute) and it usually works well (as in the example). – Spektre May 17 '16 at 15:20
  • @NathanBierema another possibility is if your gfx object uses transponed matrices ... in that case the order of transformations is reversed ... so the scr2obj and obj2scr work reversed – Spektre May 17 '16 at 15:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112190/discussion-between-nathan-bierema-and-spektre). – Nathan Bierema May 17 '16 at 15:22