locked
GOOD pinch zoom? RRS feed

  • Question

  • I've written my own pinchzoom code, which is alright, but I'm trying to get it perfect (like on the iphone).

    This article shows how to do it in XNA (http://adtsai.blogspot.com/2010/09/pinch-zooming-using-xna4-on-wp7-getting.html) and I thought I had a good translation, but I couldn't make it work.

    Does anyone have a silverlight-based translation of that article's code, or a GOOD pinch implementation?

    By good, I mean that whatever was initially below the fingers at the start of the zoom is still below them at the at the zoom?

    Thanks

    Wednesday, January 19, 2011 7:22 PM

Answers

All replies

  • Have you tried the one in the Windows Phone Silverlight Toolkit? Look at the GestureService/GestureListener component. It has a sample you can download, but also look at this article: Windows Phone 7: Pinch Gesture Sample


    Richard
    Wednesday, January 19, 2011 7:56 PM
  • Yes, of course- that's what I'm using and I'm lacking the method for matching the zoom with the position of your fingers. My code slightly magnifies the value.

    And if you try the code for the link you posted (which I've also tried)- it does the very same thing. If you start out pinching something's eyes, at the end of the pinch, your fingers aren't on top of the eyes anymore.

    See what I'm saying?
    Wednesday, January 19, 2011 8:38 PM
  • The problem in the sample supplied with the Toolkit is that the RenderTransformOrigin is hardcoded to "0.5,0.5", which means that transforms are applied relative to the center of the control. Pinch actions should are better applied to the midpoint between the two touch points. Try the following modifications to the PhoneToolkit solution.

    The toolkit's GestureSample page demonstrates gestures using a Border control. I replaced the Border control with an Image to view the pinch action better:
    GestureSample.xaml
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
                <StackPanel> 
                    <TextBlock Text="Tap to center" Style="{StaticResource PhoneTextNormalStyle}"/> 
                    <TextBlock Text="Tap and hold to reset" Style="{StaticResource PhoneTextNormalStyle}"/> 
                    <TextBlock Text="Touch and move to drag" Style="{StaticResource PhoneTextNormalStyle}"/> 
                    <TextBlock Text="Pinch (touch with two fingers) to scale and rotate" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap"/> 
                    <TextBlock Text="Flick (drag and release the touch while still moving) will show flick data on bottom of screen." Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap"/> 
                </StackPanel> 
                <TextBlock x:Name="flickData" Text="Flick:" Style="{StaticResource PhoneTextNormalStyle}" VerticalAlignment="Bottom"/> 
                <Image x:Name="image" Source="/koala.jpg" RenderTransformOrigin="0.5,0.5" CacheMode="BitmapCache"
                    <Image.RenderTransform> 
                        <CompositeTransform x:Name="transform"/> 
                    </Image.RenderTransform> 
                    <toolkit:GestureService.GestureListener> 
                        <toolkit:GestureListener  
                            Tap="OnTap" Hold="OnHold" 
                            DragStarted="OnDragStarted" DragDelta="OnDragDelta" DragCompleted="OnDragCompleted" 
                            Flick="OnFlick" 
                            PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" PinchCompleted="OnPinchCompleted"/> 
                    </toolkit:GestureService.GestureListener> 
                </Image> 
            </Grid> 

    In OnPinchStarted, I set RenderTransformOrigin to midway between the touch points. Note that RenderTransformOrigin uses values between 0.0 and 1.0 representing the (x,y) proportional position in the control.
    GestureSample.xaml.cs
        public partial class GestureSample : PhoneApplicationPage 
        { 
            double initialAngle; 
            double initialScale; 
     
            public GestureSample() 
            { 
                InitializeComponent(); 
            } 
     
            private void OnTap(object sender, GestureEventArgs e) 
            { 
                transform.TranslateX = transform.TranslateY = 0; 
            } 
     
            private void OnDoubleTap(object sender, GestureEventArgs e) 
            { 
                transform.ScaleX = transform.ScaleY = 1; 
            } 
     
            private void OnHold(object sender, GestureEventArgs e) 
            { 
                transform.TranslateX = transform.TranslateY = 0; 
                transform.ScaleX = transform.ScaleY = 1; 
                transform.Rotation = 0; 
            } 
     
            private void OnDragStarted(object sender, DragStartedGestureEventArgs e) 
            { 
                image.Opacity = 0.3; 
            } 
     
            private void OnDragDelta(object sender, DragDeltaGestureEventArgs e) 
            { 
                transform.TranslateX += e.HorizontalChange; 
                transform.TranslateY += e.VerticalChange; 
            } 
     
            private void OnDragCompleted(object sender, DragCompletedGestureEventArgs e) 
            { 
                image.Opacity = 1.0; 
            } 
     
            private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e) 
            { 
                Point point0 = e.GetPosition(image, 0); 
                Point point1 = e.GetPosition(image, 1); 
                Point midpoint = new Point((point0.X + point1.X) / 2, (point0.Y + point1.Y) / 2); 
                image.RenderTransformOrigin = new Point(midpoint.X / image.ActualWidth, midpoint.Y / image.ActualHeight); 
                initialAngle = transform.Rotation; 
                initialScale = transform.ScaleX; 
                image.Opacity = 0.8; 
            } 
     
            private void OnPinchDelta(object sender, PinchGestureEventArgs e) 
            { 
                transform.Rotation = initialAngle + e.TotalAngleDelta; 
                transform.ScaleX = transform.ScaleY = initialScale * e.DistanceRatio; 
            } 
     
            private void OnPinchCompleted(object sender, PinchGestureEventArgs e) 
            { 
                image.Opacity = 1.0; 
            } 
     
            private void OnFlick(object sender, FlickGestureEventArgs e) 
            { 
                flickData.Text = string.Format("{0} Flick: Angle {1} Velocity {2},{3}"
                    e.Direction, Math.Round(e.Angle), e.HorizontalVelocity, e.VerticalVelocity); 
            } 
        } 


    Richard
    Wednesday, January 19, 2011 11:45 PM
  • After quite a bit of sweating and cursing I came to a working implementation of the "Gold Standard" in Silverlight. I just posted the solution on my blog: http://www.frenk.com/2011/03/windows-phone-7-correct-pinch-zoom-in-silverlight/. I hope it can be useful.

    Richard, your solution is a step forward but still not completely correct. If you look at my code, in the end it's rather complicated to get it right.
    Thursday, March 3, 2011 8:52 AM
  • After quite a bit of sweating and cursing I came to a working implementation of the "Gold Standard" in Silverlight. I just posted the solution on my blog: http://www.frenk.com/2011/03/windows-phone-7-correct-pinch-zoom-in-silverlight/. I hope it can be useful.


    Nice, I'll try it out and let you know!

    > but as soon as you pinch the image a second time you realize the image moves around.

    Yep, that's exactly what I was running into.
    Thursday, March 3, 2011 2:57 PM
  • I implemented the above code from frenk.com. It's the best I've seen so far, but the performance is way too slow - compared to the default picture app on a WP7 device. There must be a better and easier way to implement this?? I'll certainly be looking for one. I'm thinking the stuttering zoom comes from slow (or too few) events firing from that toolkit - I'm thinking that frenk's code would be able to update the picture must faster / smoother if more events were fired in a faster pace... but then I'm just guessing here.
    Wednesday, November 16, 2011 3:40 AM
  • Hi Jonny,

    Did you set CacheMode="BitmapCache" for the Image control? It will help with performance.

    I also suggest you look at these articles, which describe a different technique for implementing pinch zoom:


    Richard Woo
    Wednesday, November 16, 2011 4:58 AM
  • CacheMode="BitmapCache"

    Thanks! The CacheMode setting seems to give a bit better performance, but honestly it's just my imagination and it's hard to tell exactly how much smoother it gets. Still the default picture app seems a bit smoother.

    Now the default picture app has some of those features that I guess I'll have to create myself. Bounce panning etc. Although the default app doesn't have zoom bouncing (zoom more than allowed but when you release it "bounces" back), such features that aren't really performance related but without them gives a kind of disturbing tactile experience, the same effect of slow performance. :-P
    Thursday, November 17, 2011 10:45 AM
  • Use RenderAtScale property of BitmapCache
    Tuesday, May 8, 2012 10:58 AM