Results 1 to 9 of 9

Thread: [RESOLVED] 2D translate + scale + rotate

  1. #1
    Frenzied Member boops boops's Avatar
    Join Date
    Nov 08
    Location
    Holland/France
    Posts
    1,981

    Resolved [RESOLVED] 2D translate + scale + rotate

    My ZoomPictureBox (see sig) provides image dragging and zooming, with zooming around an arbitrary point such as the mouse position. I'm now trying to do something similar in WPF, but with image rotation too. Unfortunately my school-trig/back-of-envelope mathematical skills are letting me down. Here's the code I'm using, which I hope will be reasonably self-explanatory:

    vb.net Code:
    1. Private Sub ZoomToPoint(zoomRatio As Double, zoomFocus As Point, rotation As Double)
    2.  
    3.     'Scale the image around the image origin:
    4.     _ZoomFactor *= zoomRatio
    5.     _Image.Width = sourceWidth * _ZoomFactor
    6.     _Image.Height = sourceHeight * _ZoomFactor
    7.  
    8.     'Calculate how far the image point which was under the zoom focus has shifted:
    9.     Dim imageX As Double = Canvas.GetLeft(_Image)
    10.     Dim imageY As Double = Canvas.GetTop(_Image)
    11.     Dim shiftX As Double = (zoomFocus.X - imageX) * (zoomRatio - 1)
    12.     Dim shiftY As Double = (zoomFocus.Y - imageY) * (zoomRatio - 1)
    13.  
    14.     'Modify the shift values to allow for rotation:
    15.         Dim cos = Math.Cos(rotation * Math.PI / 180)
    16.     Dim sin = Math.Sin(rotation * Math.PI / 180)
    17.     Dim shiftRX As Double = cos * shiftX - sin * shiftY
    18.     Dim shiftRY As Double = sin * shiftX + cos * shiftY
    19.  
    20.     'Translate the image to cancel the shift:
    21.     Canvas.SetLeft(_Image, Canvas.GetLeft(_Image) - shiftRX)
    22.     Canvas.SetTop(_Image, Canvas.GetTop(_Image) - shiftRY)
    23. End Sub
    24.  
    25. 'rotation is performed separately:
    26.     _Image.RenderTransform = New RotateTransform(_Rotation, _Image.Width / 2, _Image.Height / 2)
    Rotation is always around the centre of the image. The above code works fine when the rotation is 0 or when the zoom focus is also at the centre of the image. But when the zoom focus is at some other position (e.g. the mouse position) the rotated image wanders off during zooming. I suspect that my rotation code (lines 15-18) needs some additional correcting factor but I can't figure it out.

    I realize that the "right" way to do this would be to use vectors and transformation matrices for everything, but my mathematical knowledge gets really hazy there. Still, if anyone wants to recommend a solution on those lines, I'll be glad to hear of it.

    BB

  2. #2
    Hyperactive Member Lenggries's Avatar
    Join Date
    Sep 09
    Posts
    326

    Re: 2D translate + scale + rotate

    I'm just taking a stab here, but if you want to know the location of a pixel in a rotated image, I think these formulas should do it.

    Given

    Origin = (x0, y0)
    Pixel = (xp, yp)
    Rotation angle = rotation
    and you want to find the location of the rotated pixel = (xr, yr):

    x' = xp-x0
    y' = yp-y0
    hypotenuse h = sqrt((x')^2+(y')^2)
    x'' = x'/h
    y'' = y'/h

    θ = arcsin(y'') (or θ = arccos(x''))
    θ' = θ + rotation (this is the step I think your code is bypassing)

    x''' = cos(θ')
    y''' = sin(θ')

    xr = h * x'''
    yr = h * y'''

    So as long as you focus your zoom over (xr, yr), you should be fine.

    Bear in mind that I am making some significant assumptions about other parts of your code, and if those assumptions are false, everything above may be useless to you.

    Good luck!

  3. #3
    Only Slightly Obsessive jemidiah's Avatar
    Join Date
    Apr 02
    Posts
    2,301

    Re: 2D translate + scale + rotate

    Your rotation code is indeed confused. Forgive me if this reply is terse, I'm a bit busy.

    Let Z denote zoomFocus, ZS denote zoomFocus after scaling, and ZSR denote it after scaling followed by rotation, all measured relative to the window. Let S be the scale factor (zoom factor), I be the upper left coordinate of the image, and C the center of the *scaled* image. It follows that...

    (Z-I)*S = ZS - I
    => ZS = (Z-I)*S + I

    [This justifies your lines 11 and 12 since...
    (Z-I)*(S-1) = (Z-I)*S - Z + I = ZS - Z = shift after only translating.]

    By definition, the rotation operator works as
    (ZSR - C) = Rot by angle (ZS - C)

    Rot by angle here is a matrix, but you've computed it correctly in your existing code, you're just plugging in (ZS - Z) instead of (ZS - C) which will only work as you say if C=Z. Now these relations and your original code will give you ZSR; the overall shift is then just ZSR - Z, and using this for your shiftR with the existing lines 21 and 22 should make everything work out.


    Notes: I haven't read Lengries' reply. I actually wrote the above last night but the server was down when I submitted.
    Last edited by jemidiah; Sep 20th, 2012 at 06:47 PM. Reason: Swapped "translated" to the more correct "scaled"
    The time you enjoy wasting is not wasted time.
    Bertrand Russell

    <- Remember to rate posts you find helpful.

  4. #4
    Frenzied Member boops boops's Avatar
    Join Date
    Nov 08
    Location
    Holland/France
    Posts
    1,981

    Re: 2D translate + scale + rotate

    Thanks a million Lenggries. Your derivation of Xr and yr looks right to me. I now realize that I need to treat the image centre, the only point which has not been affected by rotation, as the origin for zooming too. Next I can scale the distance of Xr , yr from the origin according to the changed zoom factor. That gives me the rotated+zoomed point Xrz, Yrz. And then I translate the image so as to bring the image point Xrz, Yrz back to Xp, Yp, making it the effective zoom focus. And finally I need to code it up and see if it really works

    @jemdiah. I only just noticed that you have replied too. I'll need to take some time to look at it, but I'm sure it will be worth it.

    cheers, BB

  5. #5
    Only Slightly Obsessive jemidiah's Avatar
    Join Date
    Apr 02
    Posts
    2,301

    Re: 2D translate + scale + rotate

    Here's my translation of my previous post into code. I have not debugged it, though I have tried to be careful.

    vb.net Code:
    1. Private Sub ZoomToPoint(zoomRatio As Double, zoomFocus As Point, rotation As Double)
    2.  
    3.     'Scale the image around the image origin:
    4.     _ZoomFactor *= zoomRatio
    5.     _Image.Width = sourceWidth * _ZoomFactor
    6.     _Image.Height = sourceHeight * _ZoomFactor
    7.  
    8.     Dim imageX As Double = Canvas.GetLeft(_Image)
    9.     Dim imageY As Double = Canvas.GetTop(_Image)
    10.     Dim centerX As Double = imageX + _Image.Width / 2
    11.     Dim centerY As Double = imageY + _Image.Height / 2
    12.    
    13.     'Compute position of focused point after scaling
    14.     Dim zoomFocusScaledX As Double = (zoomFocus.X - imageX) * _ZoomFactor + imageX
    15.     Dim zoomFocusScaledY As Double = (zoomFocus.Y - imageY) * _ZoomFactor + ImageY
    16.  
    17.     'Compute position of focused point after also rotating
    18.     Dim cos = Math.Cos(rotation * Math.PI / 180)
    19.     Dim sin = Math.Sin(rotation * Math.PI / 180)
    20.     Dim rotX = cos * (zoomFocusScaledX - centerX) - sin * (zoomFocusScaledY - centerY)
    21.     Dim rotY = sin * (zoomFocusScaledX - centerX) + cos * (zoomFocusScaledY - centerY)
    22.     Dim zoomFocusScaledRotatedX As Double = rotX + centerX
    23.     Dim zoomFocusScaledRotatedY As Double = rotY + centerY
    24.    
    25.     'Modify the shift values to allow for rotation:
    26.     Dim shiftRX As Double = zoomFocusScaledRotatedX - zoomFocus.X
    27.     Dim shiftRY As Double = zoomFocusScaledRotatedY - zoomFocus.Y
    28.  
    29.     'Translate the image to cancel the shift:
    30.     Canvas.SetLeft(_Image, Canvas.GetLeft(_Image) - shiftRX)
    31.     Canvas.SetTop(_Image, Canvas.GetTop(_Image) - shiftRY)
    32. End Sub
    The time you enjoy wasting is not wasted time.
    Bertrand Russell

    <- Remember to rate posts you find helpful.

  6. #6
    Frenzied Member boops boops's Avatar
    Join Date
    Nov 08
    Location
    Holland/France
    Posts
    1,981

    Re: 2D translate + scale + rotate

    Hi jemediah, I had trouble following your terse notation, so I was very glad to see your VB.Net version of it. Unfortunately, I have been unable to get it to work, even after much trying of variations. I went on to make a further attempt at using Lenggries' approach, which I find easier to visualize. That too was unsuccessful despite much effort.

    At a certain point, I realized that the WPF Mouse.GetPosition(UIElement) returns the image point regardless of the transformations applied. This prompted me to bite the bullet and start using WPF transforms as apparently intended. I ended up with the following code, which now includes the MouseWheel sub to make the usage clearer.

    vb.net Code:
    1. Private Sub _Image_MouseWheel(sender As Object, e As System.Windows.Input.MouseWheelEventArgs) Handles Canvas1.MouseWheel
    2.     Select Case _MouseWheelMode
    3.         Case WheelMode.ZoomToImageCentre, WheelMode.ZoomToMousePos
    4.             _ZoomFactor *= 1 + e.Delta / 4000
    5.             ZoomRotateImage()
    6.         Case WheelMode.Rotate
    7.             _Rotation += e.Delta / 1000
    8.             ZoomRotateImage()
    9.     End Select
    10. End Sub
    11.  
    12. Private Sub ZoomRotateImage()
    13.  
    14.     Dim centre As New Point(_Image.Width / 2, _Image.Height / 2)
    15.     Dim zoomFocus As Point
    16.  
    17.     If _MouseWheelMode = WheelMode.ZoomToMousePos Then
    18.         zoomFocus = Mouse.GetPosition(_Image)
    19.     Else
    20.         zoomFocus = centre
    21.     End If
    22.  
    23.     Dim tg As New TransformGroup
    24.  
    25.     'zoom around zoom focus:
    26.     tg.Children.Add(New TranslateTransform(-zoomFocus.X, -zoomFocus.Y))
    27.     tg.Children.Add(New ScaleTransform(_ZoomFactor, _ZoomFactor))
    28.     tg.Children.Add(New TranslateTransform(zoomFocus.X, zoomFocus.Y))
    29.  
    30.     'rotate around image centre:
    31.     tg.Children.Add(New RotateTransform(_Rotation, centre.X, centre.Y))
    32.  
    33.     _Image.RenderTransform = tg
    34.  
    35. End Sub
    Does it work? Yes, it does - but only for a while. Zooming in and out to the mouse position starts off working perfectly but becomes increasingly unstable. Zooming out is particularly sensitive, and at a certain point the reduced image suddenly flits off to the nether regions of cyberspace, never to return. This is quite different from my earlier efforts, where the aberrant behaviour is immediately visible.

    I'm certain that the problem is getting the focus from the transformed image (line 18). I guess it introduces a positive feedback resulting in chaotic behaviour. I have also tried clicking in the image to set the zoom focus. It's not what I want (the user should just point to the zoom focus without clicking) and it is shows has some wild behaviour (the image jumps to a wrong position after clicking or on zooming out). But I think it will be worth the effort of further debugging.

    BB

  7. #7
    Only Slightly Obsessive jemidiah's Avatar
    Join Date
    Apr 02
    Posts
    2,301

    Re: 2D translate + scale + rotate

    I think I've misunderstood your situation. I don't know enough about these WPF systems to want to debug your current solution. Alternatively, you could describe precisely what situation you have and what you want to have happen, in the process hopefully clearing up my misunderstanding. I could then modify my code accordingly. Best of luck either way.
    The time you enjoy wasting is not wasted time.
    Bertrand Russell

    <- Remember to rate posts you find helpful.

  8. #8
    Frenzied Member boops boops's Avatar
    Join Date
    Nov 08
    Location
    Holland/France
    Posts
    1,981

    Re: 2D translate + scale + rotate

    If these transforms work in WPF, the same ought to be possible in GDI+ (using Graphics.Transform plus TransformPoints to get the mouse location in image coordinates). I assume the same would be true in DirectX or XNA although I'm not at all versed in those. In fact I am only just beginning to learn WPF, which seemed a safer (or at least better documented) choice for accelerated graphics, but I may return to GDI+ or DirectX at some point. I expect that the same problem of instability will invariably arise when getting the mouse position through the transform. Unfortunately, tiny errors in transforming can result in bizarre behaviour on the screen, which makes the approach very hard to debug.

    I couldn't get my ZoomPictureBox to work for zooming to the mouse position until I decided to apply the zoom incrementally: that is, calculating the shift relative to the previous position after multiplying the scaling factor by an increment (= 1 + e.Delta/K in my MouseWheel sub). Without rotation, the arithmetic turned out to be relatively easy once solved. That is why I used the zoomRatio rather than zoom factor in the first code I posted. This may prove a better approach when rotating as well as zooming. I haven't seen a direct way to do incremental zooming/rotating in the available graphics transformation methods. That is why I would like to get a grip on the mathematics behind them.

    BB

  9. #9
    Frenzied Member boops boops's Avatar
    Join Date
    Nov 08
    Location
    Holland/France
    Posts
    1,981

    Re: 2D translate + scale + rotate

    It's now working fine. I went back to the approach I was using in ZoomPictureBox, which was to operate on the bounding box of the image. That makes panning and zooming around an arbitrary point independent of whatever goes on inside the rectangle. In this case, what goes on is rotation of the image, which itself affects the size of the bounding rectangle:
    Code:
    _ImageBounds.Width = CInt(w * cos + h * sin)
    _ImageBounds.Height = CInt(w * sin + h * cos)
    where sin and cos are the absolute values of the Sine and Cos of the angle of rotation, and w and h are the width and height before rotation. The bounding box is then recentred by translating it by minus half the x and y size increase. Mathematically, this must come down to the same as other approaches but I find it easier to picture. In Windows Forms, the bounding rectangle has an additional advantage of being the rectangle to be invalidated (with a little inflation).

    I presume this approach could be usable for other kinds of transformation such as 3D rotations. It's only necessary work out the resulting value of the bounding rectangle, possibly using inverse transformation. Still, the devil is always in the coding detail.

    Thanks again to Langgries and Jemediah for helping me think this through.

    BB
    Last edited by boops boops; Oct 2nd, 2012 at 09:00 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •