0

I have an owner-draw PictureBox (picGrid) and I want to allow the user to zoom using the mouse wheel. That part is easy. But I want to automatically scroll such that the logical point under the mouse is still the logical point under the mouse after the zoom.

Here's what I have so far. It almost works but is just not right. XScroll and YScroll are my scroll positions and will be used to set the scroll bar (in addition to offset the image).

/// <summary>
/// Sets the current zoom percent.
/// </summary>
/// <param name="percent">The percent that the zoom should be set (100 == 1:1 scaling).</param>
/// <param name="x">X coordinate of the mouse in pixels.</param>
/// <param name="y">Y coordinate of the mouse in pixels.</param>
public void SetZoom(int percent, int x, int y)
{
    // If needed, this method translates mouse coordinate to logical coordinates
    // but doesn't appear to be needed here.
    //TranslateCoordinates(ref x, ref y);

    // Calculate new scale (1.0f == 1:1 scaling)
    float newDrawScale = (float)percent / 100;

    // Enforce zoom limits
    if (newDrawScale < 0.1f)
        newDrawScale = 0.1f;
    else if (newDrawScale > 1500.0f)
        newDrawScale = 1500.0f;

    // Set new zoom (if it's changed)
    if (newDrawScale != DrawScale)
    {
        DrawScale = newDrawScale;

        // *** Here's the part that isn't right ***

        // Scroll to keep same logical point under mouse
        float delta = (DrawScale - 1f);
        XScroll = (x * delta);
        YScroll = (y * delta);

        // Reflect change in scrollbars
        UpdateScrollbars();

        // Redraw content
        picGrid.Invalidate();
    }
}

How can I calculate the new scroll position so that the same logical point remains under the mouse pointer after the zoom?

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • see [Zooming graphics based on current mouse position](https://stackoverflow.com/a/37269366/2521214) – Spektre Apr 25 '20 at 07:45
  • 1
    @Spektre: Thanks. I spent a lot of time looking at that answer (which I see is yours). Some things were confusing to me. For example, the variable names were not very descriptive. Also, your `scr2obj()` and `obj2scr()` methods add `x0`,`y0` where I needed to subtract them, and subtracted them where I needed to add them. In addition, I don't know what *gfx objects* are and so I didn't understand the two versions of the code. However, I was finally able to come up with a working solution based on your approach. I appreciate it. I've posted my working code in case anyone else can benefit. – Jonathan Wood Apr 25 '20 at 19:15
  • Yep there are more ways how to transform between screen and world as you saw its not just positive/negative `offset`, its also if you apply `offset` before or after `zoom`, If the `zoom` is direct scale or reciproc like `1/zoom` these gives you `2^3=8` combinations of equation. If you add transform matrices there are more possible notations on top of all this making this a mess for rookies that just copy paste code without knowing what is going on. – Spektre Apr 26 '20 at 06:36

1 Answers1

1

I was able to work through this. This is what I came up with. (Note that XOffset and YOffset are in screen coordinates to match the scrollbars.)

public void SetZoom(int percent, int x, int y)
{
    // Calculate new scale (100% == 1:1)
    float newDrawScale = (float)percent / 100;

    // See if scale has changed
    if (newDrawScale != DrawScale)
    {
        // Update scale and adjust offsets
        ScreenToWorld(x, y, out float logicalX, out float logicalY);
        DrawScale = newDrawScale;
        WorldToScreen(logicalX, logicalY, out float screenX, out float screenY);
        XOffset += (screenX - x);
        YOffset += (screenY - y);

        // Update scrollbars and redraw
        UpdateScrollbars();
        picGrid.Invalidate();
    }
}

private void WorldToScreen(float srcX, float srcY, out float destX, out float destY)
{
    destX = (srcX * DrawScale) - XOffset;
    destY = (srcY * DrawScale) - YOffset;
}

private void ScreenToWorld(float srcX, float srcY, out float destX, out float destY)
{
    destX = (srcX + XOffset) / DrawScale;
    destY = (srcY + YOffset) / DrawScale;
}
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466