根据鼠标位置放大窗口

Posted

技术标签:

【中文标题】根据鼠标位置放大窗口【英文标题】:Zooming into a window based on the mouse position 【发布时间】:2012-11-09 21:46:34 【问题描述】:

您好,我目前正在用 C++ 编写一个 win32 应用程序,但在放大窗口内容时确实遇到了问题。这是我开始完成缩放的伪代码:

// point One
int XPointOne = -200;
int YPointTwo = 0;

// point Two
int XPointTwo = 200;
int YPointTwo = 0;

// Draw point function.
DrawPoint(XCoordinate * ScalingFactor, YCoordinate * ScalingFactor) 
    ....

我的坐标系设置为在窗口的中心有它的原点。我想在使用鼠标滚轮时进行缩放。上述解决方案的问题是缩放总是从窗口的中心发生。当您的鼠标不在窗口的中心时,这有点难看。我想放大鼠标所在的区域,但找不到合适的算法来计算 x 和 y 方向的偏移量。例如,如果鼠标具有坐标 (-200, 0),则点 1 应具有坐标 (-200, 0),点 2 应具有坐标 (600, 0),缩放因子为 2。我已经尝试了很多东西,但没有让它工作,尤其是当鼠标在缩放之间移动到其他位置时,一切都搞砸了。有谁知道如何解决这个问题?

这是我的应用程序的一些示例代码。第一个 sn-p 是我处理 WM_MOUSEWHEEL 消息的回调函数。

VOID OnMouseWheel(WPARAM const& WParam, LPARAM const& LParam) 
    if(GET_WHEEL_DELTA_WPARAM(WParam) > 0)
    
        // Zoom in
        Draw.ScaleFactor += 0.1;
    
    else
    
         // Zoom out
    

Draw 只是一个封装 GDI 函数的类。它有一个比例因子成员。下面的 sn-p 是我的 Draw 对象的 DrawCircle 成员函数,它使用比例因子在屏幕上正确呈现圆。

VOID DrawCircle(DOUBLE const& XCoordinate, DOUBLE const& YCoordinate, DOUBLE const& Radius, COLORREF const& Color) 
    HBRUSH Brush = CreateSolidBrush(Color);
    HBRUSH OldBrush = (HBRUSH)SelectObject(this->MemoryDC, Brush);

    Ellipse(this->MemoryDC, (INT) ((XCoordinate - Radius) * this->ScaleFactor), 
        -(INT)((YCoordinate + Radius) * this->ScaleFactor), 
         (INT)((XCoordinate + Radius) * this->ScaleFactor), 
        -(INT)((YCoordinate - Radius) * this->ScaleFactor)); 
    SelectObject(this->MemoryDC, OldBrush);
    DeleteObject(Brush);
 

如您所见,我的 DrawCircle 函数在应用当前比例因子时没有考虑鼠标位置。

编辑

好的,我更接近解决方案,这里是我的鼠标回调函数的更新版本。

VOID OnMouseWheel(WPARAM const& WParam, LPARAM const& LParam) 
    // Get Mouse position in real coordinates and not window coordinates.
    INT XOffset = (Window.GetClientWidth() / -2) + XMousePos;
    INT YOffset = (Window.GetClientHeight() / 2) - YMousePos;


    if(GET_WHEEL_DELTA_WPARAM(WParam) > 0)
    
        Draw.ScaleFactor += 0.1;
        Draw.XOffsetScale = -XOffset * (Draw.ScaleFactor - 1.0);
        Draw.YOffsetScale = YOffset * (Draw.ScaleFactor - 1.0);
    
    else
    
        // ...
    

这是画圆的函数。

VOID DrawCircle(DOUBLE const& XCoordinate, DOUBLE const& YCoordinate, DOUBLE const& Radius, COLORREF const& Color) 
        HBRUSH Brush = CreateSolidBrush(Color);
        HBRUSH OldBrush = (HBRUSH)SelectObject(this->MemoryDC, Brush);

        Ellipse(this->MemoryDC, (INT) ((XCoordinate - Radius) * this->ScaleFactor + XOffsetScale) , 
            -(INT)((YCoordinate + Radius) * this->ScaleFactor - YOffsetScale), 
            (INT)((XCoordinate + Radius) * this->ScaleFactor + XOffsetScale), 
            -(INT)((YCoordinate - Radius) * this->ScaleFactor - YOffsetScale)); 
        SelectObject(this->MemoryDC, OldBrush);
        DeleteObject(Brush);
 

只要我将鼠标保持在同一位置,这确实有效,但是当我移动到另一个位置时,它不会按预期缩放一次,之后它会再次正确缩放。也许这有点帮助。

提前致谢!

已解决

好的, 我现在解决了我的问题。我只是根据鼠标位置乘以缩放因子移动了坐标系的原点。感谢您的回答。

【问题讨论】:

这和winapi有什么关系?您没有提供任何有意义的代码来理解您的问题! 添加了一些我的代码的 sn-ps。抱歉,但我认为这段代码通常无助于解决问题。 您是要链接缩放,还是只能执行一次,然后返回全视图? 链接应该是可能的。一旦我已经开始工作就缩放。问题是我缩放一次-> 工作,然后我在不同的鼠标位置再次缩放,它不再工作了。当我保持鼠标位置时,我还可以更频繁地缩放。 【参考方案1】:

最“通用”的解决方案使用矩阵变换,但这里有一个简化的解释。以下伪代码可能会对您有所帮助:

/*
    VARIABLES (all in space coordinates, not pixel coordinates):

      input:
        viewRect = rectangle of the viewed area
        zoomFactor = factor of zoom relative to viewRect, ex 1.1
        mousePos = position of the mouse

      output:
        zoomedRect = viexRect after zoom
*/

/*
    A little schema:

      viewRect
    *-----------------------------------------------------------------------*
    |                       ^                                               |
    |                       | d_up                                          |
    |        zoomedRect     v                                               |
    |      *-----------------------------------------*                      |
    |d_left|                                         |       d_right        |
    |<---->|                mousePos                 |<-------------------->|
    |      |                    +                    |                      |
    |      |                                         |                      |
    |      |                                         |                      |
    |      *-----------------------------------------*                      |
    |                       ^                                               |
    |                       |                                               |
    |                       |                                               |
    |                       | d_down                                        |
    |                       |                                               |
    |                       v                                               |
    *-----------------------------------------------------------------------*

    dX = d_left + d_right
    dY = d_up + d_down
    The origin of rects is the upper left corner.
*/

/*
    First, find differences of size between zoomed rect and original rect
    Here, 1 / zoomFactor is used, because computations are made relative to the
    original view area, not the final rect):
*/
dX = viewRect.width * (1 - 1 / zoomFactor)
dY = viewRect.height * (1 - 1 / zoomFactor)

/*
    Second, find d_* using the position of the mouse.
    pX = position of the mouse along X axis, relative to viewRect (percentage)
    pY = position of the mouse along Y axis, relative to viewRect (percentage)
    The value of d_right and d_down is not computed because is not directly needed
    in the final result.
*/
pX = (mousePos.X - viewRect.X) / viewRect.width
pY = (mousePos.Y - viewRect.Y) / viewRect.height

d_left = pX * dX
d_up = pY * dY

/*
    Third and last, compute the output rect
*/
zoomedRect = viewRect
zoomedRect.X += d_left
zoomedRect.Y += d_up
zoomedRect.width -= dX
zoomedRect.height -= dY

// That's it!

对于您的问题,您需要将视图(您的窗口)与场景(绘制的对象)分开。您应该有一个绘制部分(或全部)场景的函数:

void drawScene(Rect viewArea);

还有一个缩放区域的函数(使用前面介绍的算法):

Rect zoomArea(Rect rectToZoom, Point zoomCenter, double factor);

现在,您的回调要简单得多:

VOID OnMouseWheel(WPARAM const& WParam, LPARAM const& LParam)

    // Get the position of the mouse relative to the window (in percent)
    double XMouseRel = XMousePos / double(Window.GetClientWidth());
    double YMouseRel = YMousePos / double(Window.GetClientHeight());

    // Get Mouse position in scene coordinates and not window coordinates.
    // viewArea is in scene coordinates
    // window = your window or your draw information on the scene
    // The following assumes that you're using a scene with X left-to-right and
    // Y top-to-bottom.
    double XMouse = window.viewArea.width * XMouseRel + window.viewArea.upperleft.X;
    double YMouse = window.viewArea.height * YMouseRel + window.viewArea.upperleft.Y;

    // Zoom parameters
    double zFactor = 0.1 * GET_WHEEL_DELTA_WPARAM(WParam);
    Rect viewArea = getViewArea(); // or something like this
    Point zCenter(XMouse,YMouse);

    // Zoom
    Rect zoomedRect = zoomArea(viewArea,zCenter,zFactor);
    drawScene(zoomedRect);

【讨论】:

+1 用于绘图和良好的解释。问题是我需要转换对象的坐标而不是用户看到的矩形。我仍在评估您的解决方案,我不确定这是否是我正在寻找的。不过还是谢谢你。 查看我的编辑。使用矩形,您可以知道渲染的场景部分。缩放只是应用于视图矩形的变换。 嘿,谢谢您的回答。问题是当我使用你的方法时,我只会拉伸原始图像并显示拉伸图像的某个区域。因此,例如,我所有的圈子都不会被缩放。但这就是我想要达到的目标。对不起,如果我不够清楚。我想缩放我的对象,因为这样它们在缩放时也会有更多的细节。 应该是“显示场景的某个区域”,并且尊重比例,所以显示场景的一部分可以让你有缩放的圆圈。【参考方案2】:

您正在尝试在平面中实现仿射变换的子集。在您的情况下,您只需要结合绘图平面的平移和缩放(缩放)。平面中仿射变换的全部可能性涉及使用 3 维矩阵,但现在我只给出您的问题所需的最小值。随意在网上查找完整的主题,这方面的文献很多。

首先,我们将声明一个 2D 向量和一些运算符:

  class vector2D 
  protected:
      /* here your class implementation details */
  public:
      vector2D(const vector2D &v);
      vector2D(float x, float y)  /* ... */ 
      vector2D operator +(const vector2D &v) const  /* ... */ 
      vector2D operator -(const vector2D &v) const  /* ... */ 
      vector2D operator *(float v) const  /* ... */ 
      bool operator ==(const vector2D &v) const  /* ... */ 
      const vector2D &operator = (const vector2D &v)  /* ... */ 
  ;

我会让你填空,如果你有的话,也可以使用你自己的课程。请注意,此界面可能不是最佳的,但我想专注于算法,而不是性能。

现在,让我们介绍一下显示转换:

我们将zf 称为缩放因子,trans 转换的转换部分,origin 原点为窗口中的视图。您提到您的坐标系统位于窗口的中心,因此原点将是窗口屏幕的中心。 从视图系统到窗口坐标的转换可以分解为两个独立的阶段:一个是显示对象的缩放和平移,我们称之为 modelview,另一个是从视图坐标到窗口坐标的转换,我们称之为投影。如果您熟悉 3D 渲染,这可以看作是一种类似于 OpenGL 中使用的机制。

投影可以描述为从窗口左上角到视图origin的简单翻译。

  vector2D project(const vector2D &v)
      return v + origin;
  

modelview 结合了平移和缩放(此时,UI 代码将只处理任意点的缩放)。

  vector2D modelview(const vector2D &v)
      return trans + (v * zf);
  

我会让你以最方便的方式整理这些函数和相关数据(zfcentretrans)。

接下来,让我们看看 UI 应该如何修改不同的数据。

基本上,您需要将点坐标从位于视图中心的坐标系更改为以缩放点为中心的系统,然后缩放它们的新坐标,然后返回视图中心。您要绘制的每个对象都必须经过这种转换。

那么公式是:

v' = (v + zp) * s - zp

其中zp是缩放点,s是缩放因子,v是系统中要缩放的点的坐标被转换,因此 v' 是生成的缩放点。

如果你想在不同的地方进行链式缩放,你需要考虑前面的缩放因子和中心:

如果c是新的缩放中心,t是当前平移,z是当前缩放因子,z2 em> 是新的缩放因子,然后我们可以计算新的全局变换:

t' = t + c * (1 - z2) z' = z * z2

这些是通过将坐标系移动到缩放中心、对变换应用缩放并返回原点而得出的。

关于缩放中心,您必须注意鼠标输入将在窗口坐标系中,因此必须转换回您的视图系统(以origin 为中心)。 以下unproject 函数正是这样做的:

 vector2D unproject(const vector2D &v)
     return v - origin;
 

最后,我们来简单实现一下根据新输入进行模型视图变换的函数:

 void onMouseWheel(float mouseX, float mouseY, bool zoom_in)
     float z2 = zoom_in? 1.1 : 1/1.1;
     vector2D m(mouseX,mouseY);
     if (! (m == origin))  // this is very likely
         trans = trans + unproject(m) * (1.0 - z2);
     
     zf *= z2;
     // here perhaps have a redraw event fired
 

如您所见,我提供了或多或少的通用代码,您必须适应 Win32 API 的特殊性。

【讨论】:

这看起来很像我的问题的解决方案!我现在要尝试实施,看看它是否有效。非常感谢! 好的,在我开始编程之前我做了一些简单的计算,但我不确定我是否正确理解了你的公式。如果我的点的坐标为 x=200 和 y=0,我的坐标系的原点在 x=0 和 y=0,初始比例因子为 1。现在我的鼠标位于 x=200 和 y=0 的位置我的新比例因子应该是 2。如果我将这些值放入你的公式中,我会得到意想不到的结果。 x'=(200+1*200+0)*1​​2=800。但是,如果鼠标与该点位于同一点,我希望该点保持在他的初始坐标处。我的公式有问题吗? 我重做了答案,但那里仍然存在错误。如果 Synxis 的解决方案适合您,请不要打扰我的解决方案。 嘿,再次感谢您的回答,并感谢您非常好的描述。正如我在主帖中已经提到的那样,我已经通过根据鼠标位置和缩放因子转换我的坐标系来解决这个问题。这现在工作得很好。无论如何,您的解决方案是一个更通用的解决方案,感谢您发布它。【参考方案3】:

您要做的是平移坐标,使鼠标点位于 (0,0),缩放坐标,然后将 (0,0) 偏移回鼠标坐标。

Ellipse(this->MemoryDC, (INT) (((XCoordinate - XMouse) - Radius) * this->ScaleFactor) + XMouse, 
    -(INT)(((YCoordinate - YMouse) + Radius) * this->ScaleFactor) + YMouse, 
     (INT)(((XCoordinate - XMouse) + Radius) * this->ScaleFactor) + XMouse, 
    -(INT)(((YCoordinate - YMouse) - Radius) * this->ScaleFactor) + YMouse); 

【讨论】:

嗯,这不起作用。我正在进一步调查以找出错误所在。

以上是关于根据鼠标位置放大窗口的主要内容,如果未能解决你的问题,请参考以下文章

word滚动鼠标滑轮会放大缩小怎么办

html鼠标悬停左侧缩小图片放大到右边

计算在鼠标光标位置放大的视图偏移

winform 如何实现鼠标位置获取picturebox的焦点,然后焦点放大

ActionScript 3 放大鼠标位置

winform中.当鼠标移到一个图片上怎样能使图片放大??