PhotoViewer with pinch & zoom, pan and flick support

A while back after scouring the net and merging some code, I created PhotoViewer that is used in my cool camera app. It might not be perfect but it works acceptably. Here’s how it work in code.

Lets start with Xaml..

<Image x:Name="imgViewer"  Stretch="UniformToFill" Width="728" Height="480">
    <Image.RenderTransform>
        <ScaleTransform x:Name="scaleTrans" ScaleX="1" ScaleY="1" />
    </Image.RenderTransform>
    <toolkit:GestureService.GestureListener>
        <toolkit:GestureListener Flick="GestureListener_Flick" 
            PinchStarted="GestureListener_PinchStart" 
            PinchDelta="GestureListener_PinchDelta" 
            PinchCompleted="GestureListener_PinchComplete"
            DragStarted="GestureListener_DragStart"
            DragDelta="GestureListener_DragDelta"
            DragCompleted="GestureListener_DragCompleted"
            Tap="GestureListener_Tap"
            DoubleTap="GestureListener_DoubleTap"/>
    </toolkit:GestureService.GestureListener>
</Image>

as you can see.. we start with Image and add a ScaleTransform. Next up with add GestureService from SL Toolkit. I am going to subscribe to some events. Here’s how the code behind looks

lets start by defining some data members

private readonly DispatcherTimer m_animationTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
private double m_friction = 0.99;
private Point m_scrollStartPoint;
private Point m_scrollTarget = new Point();
private double m_hvelocity;
private double m_vvelocity;
private double m_angle;
private double _cx, _cy;

Next up, set the Tick event in the constructor so we can do something on Tick of the DispatcherTimer.

m_animationTimer.Tick += new EventHandler(m_animationTimer_Tick);

Now the real code.. lets implement some of the event handlers

void m_animationTimer_Tick(object sender, EventArgs e)
{
    if (m_hvelocity < 0.1)
        m_hvelocity = 0;

    if (m_vvelocity < 0.1)
        m_vvelocity = 0;

    if (m_hvelocity == 0 && m_vvelocity == 0)
    {
        m_animationTimer.Stop();
        return;
    }

    if (m_angle > 0 && m_angle < 180)
        scaleTrans.CenterY -= m_vvelocity;
    else
        scaleTrans.CenterY += m_vvelocity;

    if(m_angle > 90 && m_angle < 270)
        scaleTrans.CenterX += m_hvelocity;
    else
        scaleTrans.CenterX -= m_hvelocity;
            
     m_hvelocity *= m_friction;
     m_vvelocity *= m_friction;

     if ((scaleTrans.CenterX <= 0 || scaleTrans.CenterX >= imgViewer.Width) || (scaleTrans.CenterY <= 0 || scaleTrans.CenterY >= imgViewer.Height))
     {
         if (scaleTrans.CenterX < 0)
             scaleTrans.CenterX = 0;

         if (scaleTrans.CenterX > imgViewer.Width)
             scaleTrans.CenterX = imgViewer.Width;

         if (scaleTrans.CenterY < 0)
             scaleTrans.CenterY = 0;

          if (scaleTrans.CenterY > imgViewer.Height)
              scaleTrans.CenterY = imgViewer.Height;

          m_animationTimer.Stop();
    }
}
        
private void GestureListener_Flick(object sender, FlickGestureEventArgs e)
{
    m_angle = e.Angle;

    m_animationTimer.Stop();

    m_scrollStartPoint = e.GetPosition(this);
    m_scrollTarget.X = m_scrollStartPoint.X + e.HorizontalVelocity;
    m_scrollTarget.Y = m_scrollStartPoint.Y + e.VerticalVelocity;
                
    m_hvelocity = Math.Abs(e.HorizontalVelocity)/100;
    m_vvelocity = Math.Abs(e.VerticalVelocity)/100;
                                
    if (m_scrollTarget.X < 0) 
        m_scrollTarget.X = 0;

    if(m_scrollTarget.X > imgViewer.Width)
        m_scrollTarget.X = imgViewer.Width;

    if (m_scrollTarget.Y < 0)
        m_scrollTarget.Y = 0;

    if (m_scrollTarget.Y > imgViewer.Height)
        m_scrollTarget.Y = imgViewer.Height;

    m_animationTimer.Start();
}

private void GestureListener_PinchStart(object sender, PinchStartedGestureEventArgs e)
{
    Point p1 = e.GetPosition(imgViewer, 0);
    Point p2 = e.GetPosition(imgViewer, 1);

    scaleTrans.CenterX = (p1.X + ((p2.X - p1.X) / 2));
    scaleTrans.CenterY = (p1.Y + ((p2.Y - p1.Y) / 2));

    _cx = scaleTrans.ScaleX;
    _cy = scaleTrans.ScaleY;
}

private void GestureListener_PinchDelta(object sender, PinchGestureEventArgs e)
{         
    // Compute new scaling factors
    double cx = _cx * e.DistanceRatio;
    double cy = _cy * e.DistanceRatio;

    // If they're between 1.0 and 4.0, inclusive, apply them
    if (cx >= 1.0 && cx <= 4.0 && cy >= 1.0 && cy <= 4.0)
    {
        if ((cy - 1) < 0.1 && (cx - 1) < 0.1)
            cx = cy = 1;
            
        scaleTrans.ScaleX = cx;
        scaleTrans.ScaleY = cy;
    }
}

private void GestureListener_PinchComplete(object sender, PinchGestureEventArgs e)
{
}

private void GestureListener_DragStart(object sender, DragStartedGestureEventArgs e)
{
}

private void GestureListener_DragDelta(object sender, DragDeltaGestureEventArgs e)
{
    scaleTrans.CenterX = (scaleTrans.CenterX - e.HorizontalChange);
    scaleTrans.CenterY = (scaleTrans.CenterY - e.VerticalChange);

    if (scaleTrans.CenterX < 0)
        scaleTrans.CenterX = 0;
    else if (scaleTrans.CenterX > (imgViewer.Height * scaleTrans.ScaleX))
        scaleTrans.CenterX = imgViewer.Height * scaleTrans.ScaleX;

    if(scaleTrans.CenterY < 0)
        scaleTrans.CenterY = 0;
    else if (scaleTrans.CenterY > (imgViewer.Height * scaleTrans.ScaleY))
        scaleTrans.CenterY = imgViewer.Height * scaleTrans.ScaleY;
}

private void GestureListener_DragCompleted(object sender, DragCompletedGestureEventArgs e)
{
    scaleTrans.CenterX = (scaleTrans.CenterX - e.HorizontalChange);
    scaleTrans.CenterY = (scaleTrans.CenterY - e.VerticalChange);

    if (scaleTrans.CenterX < 0)
        scaleTrans.CenterX = 0;
    else if (scaleTrans.CenterX > imgViewer.Width)
        scaleTrans.CenterX = imgViewer.Width;

    if (scaleTrans.CenterY < 0)
        scaleTrans.CenterY = 0;
    else if (scaleTrans.CenterY > (imgViewer.Height))
        scaleTrans.CenterY = imgViewer.Height;
}

private void GestureListener_DoubleTap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
    scaleTrans.ScaleX = scaleTrans.ScaleY = 1;
}

private void GestureListener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
{
    m_animationTimer.Stop();
    m_hvelocity = 0;
    m_vvelocity = 0;
}

10 thoughts on “PhotoViewer with pinch & zoom, pan and flick support

  1. Pingback: [Windows Phone] PhotoViewer with pinch & zoom, pan and flick support

  2. Pingback: PhotoViewer with pinch & zoom, pan and flick support | Hispafoxing

    • Its been deprecated since Mango Release ? that’s almost 2 years now. I’d probably use it as long as its still there. Having said that, if I was designing something for Windows 8 and had to implement something, I use Manipulation Events (Started, Delta and Complete) to create one and share some code

  3. Pingback: How to limiting the image zooming, dragging with the screen? - Popular Windows Phone Questions

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s