Window scaling based on mouse position

Hi, I am writing a win32 application in C ++ at the moment, and I really have a problem to zoom in the contents of my window. Here is the pseudo code that I started by doing the scaling:

// point One int XPointOne = -200; int YPointTwo = 0; // point Two int XPointTwo = 200; int YPointTwo = 0; // Draw point function. DrawPoint(XCoordinate * ScalingFactor, YCoordinate * ScalingFactor) { .... } 

My coordinate system is set so that its origin is in the center of the window. I would like to zoom in when using the mouse wheel. The problem with the solution above is that scaling always comes from the center of the window. This view looks ugly when your mouse is not in the center of the window. I would like to enlarge the area in which the mouse is located, but I cannot find a suitable algorithm for shifting offsets in the x and y directions. For example, if the mouse has coordinates (-200, 0), then the point should have coordinates (-200, 0), and two points should have coordinates (600, 0) with a zoom factor of two. I already tried a lot of things, but did not get it to work, especially when the mouse moves to other places between scaling, everything becomes damaged. Does anyone know how to solve this problem?

Here is a sample code of my applicator. The first snippet is my callback function to process the WM_MOUSEWHEEL message.

 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 is just a class that wraps GDI functions. It has a member of the scaling factor. The following is a snippet of the DrawCircle function of my Draw object using a scale factor to display the circle correctly on the screen.

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

As you can see, my DrawCircle function does not take mouse position into account when applying the current scale factor.

EDIT

Ok, I came close to a solution, here is an updated version of my mouse callback function.

 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 { // ... } } 

And here is the function that draws the circle.

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

This works as long as I hold the mouse in the same position, but when I move to another position, it does not scale as expected after it scales correctly again. Perhaps this helps a bit.

Thanks in advance!

solvable

Ok, now I solved the problem. I simply moved the origin of my coordinate system based on the position of the mouse multiplied by the zoom factor. Thank you for your responses.

+4
source share
3 answers

You are trying to implement a subset of affine transformations in the plane. In your case, you only need to combine translation and scaling (scaling) of the drawing plane. The full set of capabilities for affine transformations in the plane includes the use of matrices of three dimensions, but for now I’ll just give the minimum necessary for your problem. Feel free to search the entire topic online, there is a lot of literature on this subject.

First of all, we will declare a 2D vector, and some operators:

  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) { /* ... */ } }; 

I will let you fill in the blanks or use your own class if you have one. Please note that this interface may not be optimal, but I want to focus on algorithms and not on performance.

Now consider mapping transformations:

We will call zf the scaling factor, trans translation part of the transformation, and the origin is the view in the window. You mentioned that your coordination system is centered in the window, so the center of the window will be the source. The conversion from the viewing system to the window coordinate can be decomposed into two separate stages: one of which will be the scaling and translation of the displayed objects, which we will call the model, and one that will be the translation from the codes of the views on the window coordinates, which we will call the projection. If you are familiar with 3D rendering, this can be seen as a similar mechanism used in OpenGL.

Projection can be described as a simple translation in the upper left corner of the window to the origin view.

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

Modelview combines translations and scaling (at the moment, the UI code will only handle scaling at arbitrary points).

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

I will let you organize these functions and the corresponding data ( zf , centre , trans ) the most convenient way for you.

Next, let's see how the various data should be changed by the user interface.

Basically, you need to change the coordinates of the point from the coordinate system located in the center of your view, to the system centered at the zoom point, then change their new coordinates, and then return to the center of view. Every object that you want to conduct must undergo this transformation.

the formula then:

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

where zp is the scaling point, s is the scaling factor, v is the coordinate of the point in the system to be transformed, and therefore v 'is the resulting enlarged point.

If you want to adjust the scaling in different places, you need to consider the case-based scaling factor and center:

if c is the new scaling center, t is the current translation, z is the current scaling factor, and z2 is the new scaling factor, then we can calculate the new global transformation using:

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

This comes from moving the coordinate system to the center of scaling, applying scaling to the transformation, and returning to the origin.

As for the center of scaling, you should be careful that the mouse input will be in the coordinate system of the window and, therefore, should be converted back to your view system (centered in origin ). The following unproject function does just that:

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

Finally, let us have a simple implementation of a function that transforms the model transformation according to a new input:

  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 } 

As you can see, I have provided more or less general code that you will have to adapt to the features of the Win32 API.

+4
source

The most common white solution uses matrix transformations, but this is a simplified explanation. The following pseudo code can help you:

 /* 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 it! 

For your problem, you need to separate the view (your window) from the scene (the objects that were selected). You should have a function that makes up part (or all) of the scene:

 void drawScene(Rect viewArea); 

and a function that scales the region (using the algorithm presented earlier):

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

Now your callback is much simpler:

 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); } 
+6
source

What you want to do is translate the coordinates so that the mouse point is at (0,0), increase the coordinates, and then shift (0,0) back to the coordinates of the mouse.

 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); 
0
source

Source: https://habr.com/ru/post/1445148/


All Articles