1

Problem - Before the zoom operation, the user can draw "points" according to the click position of the mouse on the canvas(picturebox), but when the user uses the mouse wheel to zoom (without returning to the original zoom ratio), the user draws a "point" with the mouse clicke again, the clicked position of the mouse is inconsistent with the position of the drawn "point" on the canvas(picturebox).

The demo code is down below:

private List<Point> _points;
private int _pointRadius = 60;
private float _zoom = 1f;
private float _offsetX = 0f;
private float _offsetY = 0f;

private void picturebox_MouseDown(object sender, MouseEventArgs e)
{
    _points.Add(e.Location);
}

private void picturebox_MouseWheel(object sender, MouseEventArgs e)
{
    if(e.Delta < 0)
    {
         _zoom += 0.1f;
         _offsetX = e.X * (1f - _zoom);
         _offsetY = e.Y * (1f - _zoom);
    }
    else
    {
         if (_zoom <= 1f)
         {
              return;
         }
         _zoom -= 0.1f;
         _offsetX = e.X * (1f - _zoom);
         _offsetY = e.Y * (1f - _zoom);
     }
     picturebox.Invalidate();
}

private void picturebox_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.TranslateTransform(_offsetX, _offsetY);
    e.Graphics.ScaleTransform(_zoom, _zoom);
    foreach (Point point in _points)
    {
         e.Graphics.FillEllipse(Brushes.Black, point.X - _pointRadius, point.Y - _pointRadius, 2 * _pointRadius, 2 * _pointRadius);
    }
}

[Edit1 - add the picturebox properties screenshot] enter image description here

[Edit2 - add a .gif to show the problem] enter image description here

I would appreciate any help, it would be better if your answer could be more detailed.

LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • https://stackoverflow.com/a/61964222/14171304 – dr.null Jul 27 '22 at 07:50
  • @dr.null Thanks for your great comment. But the post is not the solution I want. My above code has been able to zoom with the mouse position as the center, but the problem is probably that the coordinates of the upper left corner of the picturebox are still (0, 0) after zooming. –  Jul 27 '22 at 08:05
  • Not clear what *effect* you want to render here, but the transformation's offset has no delta (you only add or subtract from the current position) and the values are inverted, it should be `e.Graphics.TranslateTransform(-_offsetX, -_offsetY);`. This can scale to the `_zoom` factor, but it's going to constantly move the shapes downwards anyway. if you want to keep the shapes fixed at mouse position (scaled or not), you need to calculate the mouse offset in relation to the previous position. – Jimi Jul 27 '22 at 13:39
  • The code you find in the first comment uses a different paradigm: it only considers the bounds of the drawing as a RectangleF, it only scales and *moves* this reference shape, draws the contents (a Bitmap or anything else) only in the end, using just basic math to calculate the scale factor and relative positions. This makes the drawing much faster (only multiplies float values and some matrices). You'll notice when you have very large Bitmaps or a lot of shapes to render. – Jimi Jul 27 '22 at 13:47
  • You're welcome and sorry for the delay. Do you have an `.Image` in the PB? If so, you need to transform and draw it as well. Maybe a screenshot would help to understand the problem. – dr.null Jul 27 '22 at 21:52
  • @dr.null I think I dont have any "real" Image in the picturebox. I just used picturebox as canvas to draw some graphics on it. But the problem is that **when I didn't do the zooming, I could draw some graphics by getting the clicked position of the mouse. After I have zoomed with the mouse wheel, the position where I clicked the mouse was deviated from the position displayed on the canvas**. –  Jul 28 '22 at 01:42
  • @Jimi The code I put above just shows my implementation of the zoom function, but now the problem is that **I seem to only zoom the drawing objects in the `Paint` event, the picturebox is not zoomed. when I have zoomed, and I checked the coordinates of the upper left corner of the picturebox, it is still (0, 0). I don't think the coordinates of the upper left corner should be (0, 0) after scaling.** –  Jul 28 '22 at 01:54
  • What does *the picturebox is not zoomed* mean? What *coordinates of the upper left corner*? Do you mean the ClientRectangle? Your scale factor is applied to the current device context (your Graphics), not the Control, which of course doesn't change either size or client size. What changes is the Graphics' `ClipBounds`. – Jimi Jul 28 '22 at 02:12
  • @Jimi Yes, that is what I want to achieve. I want the picturebox and the drawing objects drawn on the picturebox to be scaled as a whole using the mouse wheel and centered on the mouse cursor. how to do that? –  Jul 28 '22 at 02:30
  • What a strange requirement! Transforming the control itself? You can in the `MouseWheel` event resize and reposition if you want the control according to the `Delta` value. But still an odd thing to do. No need to mention that you can't rotate controls. – dr.null Jul 28 '22 at 06:56
  • @dr.null Maybe I didn't express it clearly. Actually, this is not an odd requirement. I don't know if you have used AutoCAD or something like that. Sometimes users need to draw something, and then zoom in or out and draw other stuff. –  Jul 28 '22 at 07:09
  • The drawings are scaled/rotated/skewed/..etc. not the drawing canvas. Period. Don't waste your time. Here's more examples to study to recalculate points/rectangles/sizes/locations after scaling. [Translate Rectangle Position in Zoom Mode Picturebox](https://stackoverflow.com/questions/53800328/translate-rectangle-position-in-zoom-mode-picturebox) - [How to draw on a zoomed image?](https://stackoverflow.com/a/39305038/14171304) - [Resizing drawlines on a paint event](https://stackoverflow.com/questions/44079331/resizing-drawlines-on-a-paint-event/44088560#44088560). – dr.null Jul 28 '22 at 07:28
  • [How to measure length of line which is drawn on image? C#](https://stackoverflow.com/a/51140133/14171304). You don't need more. Just a PB with the size mode property set to auto. – dr.null Jul 28 '22 at 07:33
  • @dr.null Thanks for your recommended. And I set the picturebox sizeMode to AutoSize. But it didn't help me solve the problem. Is there any wrong? I am reading this one you recommended [How to measure length of line which is drawn on image? C#](https://stackoverflow.com/a/51140133/14171304), and I found some the picturebox zoom operation. If you already know how to solve my problem, you can answer in detail, thank you very much. –  Jul 28 '22 at 08:17
  • Don't tell me you are not hosting the PB by a `Panel` with its `AutoScroll` property set to `true` ? I mean to get a scrollable Image/drawings. I'm sorry for keep asking because I really don't know what context/layout you have. You need to _scroll_ vertically and horizontally to access the zoomed-non-visible regions. – dr.null Jul 28 '22 at 09:02
  • @dr.null Yes, the `picturebox` is loaded in a `Panel`, and property `AutoScroll = true` for the `Panel`, property `Dock = None` for the `Picturebox`. When I run the program, there are scroll bars on the right and bottom of the `Picturebox`, but zooming doesn't automatically make the scroll bars shorter or longer or move. Shall we do some calculations for the scrollbars size and moving or something else? –  Jul 29 '22 at 01:06
  • @dr.null Maybe I found a same [problem](https://stackoverflow.com/questions/37262282/zooming-graphics-based-on-current-mouse-position) as mine. But I dont understand C language quite. So would you mind to combine my code and give a C# code that solves the problem? If so, I would love to vote for you. –  Aug 02 '22 at 02:20
  • You haven't answered yet. You have mentioned AutoCad, so I need to ask you again, do you have an image displayed in the background? `.Image` or `.BackgroundImage` **OR** you just drawing shapes by mouse?. Screenshot your Form (Shift+WindowsKey+S) please. – dr.null Aug 02 '22 at 05:05
  • @dr.null I modified my question and uploaded a screenshot of the properties of the Form. I just drawing shapes by mouse in the Canvas(a picturebox). If you still need any details please let me know, I hope it will be convenient for you to solve my problem. Thanks again. –  Aug 02 '22 at 05:48

1 Answers1

0

Based on the referred problem, to scale and offset the view to the mouse wheel position:

private float zoom = 1f;
private PointF wheelPoint;

private void picturebox_MouseWheel(object sender, MouseEventArgs e)
{
    zoom += e.Delta < 0 ? -.1f : .1f;
    zoom = Math.Max(.1f, Math.Min(10, zoom));            
    wheelPoint = new PointF(e.X * (zoom - 1f), e.Y * (zoom - 1f));
    picturebox.Invalidate();
}

Offset and scale before drawing your shapes:

private void picturebox_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;

    g.TranslateTransform(-wheelPoint.X, -wheelPoint.Y);
    g.ScaleTransform(zoom, zoom);

    // Draw....
}

SO73133298


If you also use the mouse inputs to draw your shapes, then you also need to take the offset into account to get the right point.

Revising the first example to mainly add the ScalePoint method to do the required calculations.

private float _zoom = 1f;
private PointF _wheelPoint;
private readonly SizeF _recSize = new Size(60, 60);
private List<RectangleF> _rects = new List<RectangleF>();

private PointF ScalePoint(PointF p) =>
    new PointF(
        (p.X + _wheelPoint.X) / _zoom - (_recSize.Width / 2),
        (p.Y + _wheelPoint.Y) / _zoom - (_recSize.Height / 2));

private void picturebox_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        _rects.Add(new RectangleF(ScalePoint(e.Location), _recSize));
        picturebox.Invalidate();
    }
}

private void picturebox_MouseWheel(object sender, MouseEventArgs e)
{
    _zoom += e.Delta < 0 ? -.1f : .1f;
    _zoom = Math.Max(.1f, Math.Min(10, _zoom));
    _wheelPoint = new PointF(e.X * (_zoom - 1f), e.Y * (_zoom - 1f));
    picturebox.Invalidate();
}

private void picturebox_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;

    g.TranslateTransform(-_wheelPoint.X, -_wheelPoint.Y);
    g.ScaleTransform(_zoom, _zoom);
    g.SmoothingMode = SmoothingMode.AntiAlias;

    _rects.ForEach(r => g.FillEllipse(Brushes.Black, r));
}

SO73133298B

dr.null
  • 4,032
  • 3
  • 9
  • 12
  • 1
    I am very honored to have your answer. But you didn't solve my problem. Maybe I didn't describe my problem clearly. So I edited the question to add the .gif. I hope I can accurately describes my problem this time. Thanks again. –  Aug 04 '22 at 07:06
  • @K.K Sure Mr. double Ks. Check out the edit. – dr.null Aug 04 '22 at 14:08
  • I tried your code. That is awesome. I think it is pretty close the answer what I was looking for. But only one thing is that the `picturebox` is placed on a `panel`, so there are scroll bars to the right and the bottom of `picturebox`. How to combine scroll bars with zooming? I've placed the expectation as a .gif in the question. –  Aug 05 '22 at 03:23
  • Anyway, I appreciate your help and I accept your answer. –  Aug 05 '22 at 14:25