根据鼠标位置放大窗口

时间:2012-11-09 21:46:35

标签: c++ winapi

我现在正在用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)点,则应该具有坐标(-200,0),并且将坐标(600,0)指向两个,比例因子为2。我已经尝试了很多东西,但没有让它工作,特别是当鼠标移动到其他位置之间缩放一切都搞砸了。谁知道如何解决这个问题?

以下是我的appliaciton的一些示例代码。第一个片段是我的回调函数,用于处理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函数的类。它有一个比例因子成员。下面的代码片段是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);
 }

只要我将鼠标保持在相同的位置,但是当我移动到另一个位置时,它确实有效,但是一旦它再次正确缩放,它就不会像预期的那样变焦。也许这有点帮助。

提前致谢!

解决

确定, 我现在解决了我的问题。我只是根据鼠标位置乘以缩放系数来移动坐标系的原点。谢谢你的回答。

3 个答案:

答案 0 :(得分:8)

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

/*
    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 :(得分:6)

您正尝试在平面中实现仿射变换的子集。在您的情况下,您只需要组合绘图平面的平移和缩放(缩放)。平面中仿射变换的全部可能性涉及使用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 是新的缩放因子,然后我们可以用:

计算新的全局变换

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的特性。

答案 2 :(得分:0)

你要做的是翻译坐标,使鼠标点位于(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);