根据当前鼠标位置进行图像缩放

6
我正在尝试根据鼠标的当前位置缩放绘图。目前我的onMouseWheel方法看起来像这样(基于这个StackOverflow答案):
    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_translateY 是成员变量。

我按照以下方式进行缩放、平移图形,然后绘制线条:

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

这个视频展示了当我尝试在某一点上放大和缩小时会发生什么。我做错了什么?

以下是测试目的的示例面板类中代码的样子:

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);
    }
}

你能上传你的示例应用程序代码吗?我认为它与你的比例有关,如果你使用_scale += 0.25f而不是乘法,它是否有效。虽然这只是一个猜测,因为我现在没有一个可工作的示例。 - dwonisch
我添加了一个示例面板类,您可以将其直接添加到表单中。但是这个添加并没有起作用。如果我缩小得太多,那么缩放会变成负数。这与翻译有关。缩放本身是正常工作的。 - Nathan Bierema
1个回答

3

太懒了,不想把公式搞对(而且很可能会犯和你一样的错误……我不知道是不是只有我这样,但是这种简单的东西我却无法处理,让我发疯)。相反,我处理这类任务的方式如下(这样更安全,不容易出错):

  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.

这里是一个简单的C++示例:

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;
    }
//---------------------------------------------------------------------------

mx,my 是鼠标在屏幕坐标系中的实际位置,x0,y0 是平移量,zoom 是缩放比例。

下面是该过程的GIF动画:

example

[编辑1] 看起来你的图形对象使用了转置矩阵

这意味着变换的顺序被反转,因此方程会有些改变... 下面是你的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; 
 } 
//---------------------------------------------------------------------------

当我使用你的代码时,它做了同样的事情。你测试过吗?如果是这样,能否提供整个类的代码? - Nathan Bierema
你在第二个方程式下的计算似乎和我的方程式做了同样的事情。_scale最终被取消,然后你乘以1.25。是不是图形坐标系和鼠标坐标系的原点不同呢? - Nathan Bierema
@NathanBierema 是的,我在编写时犯了一个错误...(就像我以前做过很多次一样...)早上匆忙之中。请看我添加的功能源代码(来自VCL,但您可以将其简单地移植到您的代码中),这是我所想的。如果即使这样也不起作用,那么您可能得到了错误的鼠标坐标(与您的应用程序无关,而是相对于桌面)。 - Spektre
好的,使用那段代码仍然不起作用。我的鼠标坐标似乎大致正确,因为如果我在(100,100)处绘制一些东西并单击它,e.X和e.Y就会在(100,100)的像素范围内(我的鼠标可能有几个像素的偏差)。我如何确定鼠标相对于Graphics对象的坐标? - Nathan Bierema
@NathanBierema 另一个可能性是,如果您的 gfx 对象使用了转置矩阵...在这种情况下,变换的顺序是相反的...因此,scr2obj 和 obj2scr 的工作方式也是相反的。 - Spektre
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接