Windows Phone 7 - Image加上Zoom in/out功能

Windows Phone 7 - Image加上Zoom in/out功能

最近不小心迷上用手機看漫畫這件事,所以想說寫一個自己的漫畫程式,這樣可以選擇自己覺得

取得圖像資料比較快且穩定的網站。不過在撰寫過程裡,對於Image的處理卻是最為困難的地方。

不過還好,在網路上找到了這一篇資料<Windows Phone 7: correct pinch zoom in Silverlight>,裡面

針對圖像的處理提供了最完整的解釋,往下針對該篇文章提到的部分,加以說明。

 

〉定義XAML中Image的呈現樣式

   1: <Grid x:Name="LayoutRoot" Background="Transparent">
   2:     <Image x:Name="ImgZoom"
   3:            Source="sample.jpg"
   4:            Stretch="UniformToFill"
   5:            RenderTransformOrigin="0,0"> 
   6:         <toolkit:GestureService.GestureListener>
   7:             <!-- 定義Pinch系列的事件,DoubleTap回到原圖,Drag圖像移動-->
   8:             <toolkit:GestureListener
   9:                     PinchStarted="OnPinchStarted"
  10:                     PinchDelta="OnPinchDelta"
  11:                     DragDelta="OnDragDelta"
  12:                     DoubleTap="OnDoubleTap"/>
  13:         </toolkit:GestureService.GestureListener>
  14:         <Image.RenderTransform>
  15:             <!-- 定義CompositeTransform支援Scale與Translate的特效 -->
  16:             <CompositeTransform
  17:                     ScaleX="1" ScaleY="1"
  18:                     TranslateX="0" TranslateY="0"/>
  19:         </Image.RenderTransform>
  20:     </Image>
  21: </Grid>

如果針對GestureService不是非常熟悉的地方,可以參考<Windows Phone 7 - 淺談手勢(Gestures)運作>的說明。

上述程式主要針對Pinch、Drag與DoubleTap三種手勢進行事件的定義,並且實作了二種RenderTransform支援

Image圖像內容的Scale、Translate二種特效。

 

Image

     在Image控件裡幾個屬性,在使用RenderTransform時需要注意的,如下:

屬性 說明
ScaleX 屬於ScaleTransform的重要屬性。
控制物件的Scale X比例(横向擴大),一種二維的X-Y坐標系統。
SacleY 屬於ScaleTransform的重要屬性。
控制物件的Scale Y比例(直向擴大),一種二維的X-Y坐標系統。
TranslateX 屬於TranslateTransform的重要屬性。
控制物件的TranslatesX比例(横向移動),一種二維的X-Y坐標系統。
TranslateY 屬於TranslateTransform的重要屬性。
控制物件的TranslatesX比例(直向移動),一種二維的X-Y坐標系統。

在處理RenderTransform裡還有包括:RotateTransformSkewTransformTranslateTransformMatrixTransform等,

這些Transform可透過TransformGroup組合起來一同管理,讓套用的物件可同時具有這些效果。

 

定義好了圖像的基本效果與XAML後,接下來便來看看,實際如何處理:Pinch、Drag與DoubleTap的手勢事件;

〉處理Pinch事件(PinchStarted與PinchDelta)

     Pinch代表運用二個手指按在螢幕上,進行二指間的拉近與拉遠移動所觸發的事件。

在<Windows Phone 7 - 淺談手勢(Gestures)運作>提到二個事件的定義,如下:

PinchStarted:代表Pinched事件的開始,即是二個手指剛觸控於螢幕時;

PinchDelta:代表Pinched事件執行的過程,即時二個手指間距移動時觸發的事件;

 

(1) 在PinchStarted記下目前圖像控件的二個手指點擊的位置與設定Scale比例為1

   1: /// <summary>
   2: /// Initializes the zooming operation
   3: /// </summary>
   4: private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
   5: {
   6:     //取得目前二個手指在圖像控件的座置,並且設定目前Scale比率為1
   7:     _oldFinger1 = e.GetPosition(ImgZoom, 0);
   8:     _oldFinger2 = e.GetPosition(ImgZoom, 1);
   9:     
  10:     //設定為1,代表以目前圖像的狀態進行Scale的調整
  11:     _oldScaleFactor = 1;    
  12: }

 

(2) 在PinchDelta進行一系列移動時的圖像Scale與座標移動的計算

   1: /// <summary>
   2: /// Computes the scaling and translation to correctly zoom around your fingers.
   3: /// </summary>
   4: private void OnPinchDelta(object sender, PinchGestureEventArgs e)
   5: {
   6:     //取得移動的距離比率 / 目前的Scale比率,取得對應的Scale比率
   7:     var scaleFactor = e.DistanceRatio / _oldScaleFactor;
   8:     //檢查新的Scale比率是否合法
   9:     if (!IsScaleValid(scaleFactor))
  10:         return;
  11:  
  12:     //取得新的二個手指座標
  13:     var currentFinger1 = e.GetPosition(ImgZoom, 0);
  14:     var currentFinger2 = e.GetPosition(ImgZoom, 1);
  15:  
  16:     //根據目前的二手指座標、上一個二手指座標、圖像的座標、新Scale比率,重新建立一個Point
  17:     var translationDelta = GetTranslationDelta(
  18:         currentFinger1,
  19:         currentFinger2,
  20:         _oldFinger1,
  21:         _oldFinger2,
  22:         ImagePosition,
  23:         scaleFactor);
  24:  
  25:     //更新儲存現在的手指座標與Scale比率
  26:     _oldFinger1 = currentFinger1;
  27:     _oldFinger2 = currentFinger2;
  28:     _oldScaleFactor = e.DistanceRatio;
  29:  
  30:     //更新圖像Scale比率與圖像座標
  31:     UpdateImageScale(scaleFactor);
  32:     UpdateImagePosition(translationDelta);
  33: }

透過在該PinchDelta擷取到的(1) 移動距離搭配目前的Scale比率,計算出的Scale比率;並且因為比率的改變,也要讓

(2) 圖像放大的位置是二指間的圖像區域,因此再透過二指間的座標與Scale比率等參數,計算新的座標來配合Scale

的變動;

 

在上述的程式裡有二個事件參數,它們均繼承GestureEventArgs加上了Pinch的部分:

     ‧PinchStartedGestureEventArgs:定義了二個觸控點在啟動時需要擷取到的基本資料;

 

屬性/方法 說明
GetPosition 回傳在相對應UIElement中二個觸控點(0或1)任一個的座標位置。
Distance 二個觸控點之間的距離。

 

     ‧PinchGestureEventArgs:定義在Pinch過程中相對於距離、角度的處理值;

屬性/方法 說明
DistanceRatio 回傳接觸點之間的當前距離/原來的接觸點之間的距離的比值。
TotalAngleDelta 回傳當前的觸摸位置和原始觸摸位置之間的角度差。

 

〉處理Drag事件

     Drag負責一個手指按在螢幕上,移動時連著帶動有監聽Drag事件的物件跟著移動。此程式增加Drag的機制讓圖像

可以跟著Drag的新座標進行移動的效果。如下程式:

   1: /// <summary>
   2: /// Moves the image around following your finger.
   3: /// </summary>
   4: private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
   5: {
   6:     //取得移動的新座標
   7:     var translationDelta = new Point(e.HorizontalChange, e.VerticalChange);
   8:  
   9:     //驗證是否超過Scale後可移動範圍、目前圖像移動與實體大小的差距
  10:     if (IsDragValid(1, translationDelta))
  11:         UpdateImagePosition(translationDelta);
  12: }

 

〉處理DoubleTab事件

     DoubleTab事件發生連續快速二次Tap(輕敲)動作於UIElement。此程式增加了在圖像上進行DoubleTab之後,將圖像復原

成原來的樣子。如下程式:

   1: /// <summary>
   2: /// Resets the image scaling and position
   3: /// </summary>
   4: private void OnDoubleTap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
   5: {
   6:     TotalImageScale = 1;
   7:     ImagePosition = new Point(0, 0);
   8:     //呼叫ApplyScale()與ApplyPosition()根據TotalImageScale與ImagePosition
   9:     //復原圖像的原始比例
  10:     ApplyScale();
  11:     ApplyPosition();
  12: }

 

〉相關的副程式

(1) 取得隨著Scale與座標改變圖像的新座標:GetTranslationDelta()

   1: /// <summary>
   2: /// Computes the translation needed to keep the image centered between your fingers.
   3: /// </summary>
   4: private Point GetTranslationDelta(
   5:     Point currentFinger1, Point currentFinger2,
   6:     Point oldFinger1, Point oldFinger2,
   7:     Point currentPosition, double scaleFactor)
   8: {
   9:     var newPos1 = new Point(
  10:      currentFinger1.X + (currentPosition.X - oldFinger1.X) * scaleFactor,
  11:      currentFinger1.Y + (currentPosition.Y - oldFinger1.Y) * scaleFactor);
  12:  
  13:     var newPos2 = new Point(
  14:      currentFinger2.X + (currentPosition.X - oldFinger2.X) * scaleFactor,
  15:      currentFinger2.Y + (currentPosition.Y - oldFinger2.Y) * scaleFactor);
  16:  
  17:     var newPos = new Point(
  18:         (newPos1.X + newPos2.X) / 2,
  19:         (newPos1.Y + newPos2.Y) / 2);
  20:  
  21:     return new Point(
  22:         newPos.X - currentPosition.X,
  23:         newPos.Y - currentPosition.Y);
  24: }

 

(2) 執行圖像Scale與Position更換的方法:ApplyScale()與ApplyPosition()

       依賴編輯好的公用變數:TotalImageScale與ImagePosition,進行圖像控件的調整;

   1: /// <summary>
   2: /// Applies the computed position to the image control.
   3: /// </summary>
   4: private void ApplyPosition()
   5: {
   6:     ((CompositeTransform)ImgZoom.RenderTransform).TranslateX = ImagePosition.X;
   7:     ((CompositeTransform)ImgZoom.RenderTransform).TranslateY = ImagePosition.Y;
   8: }
   9:  
  10: /// <summary>
  11: /// Applies the computed scale to the image control.
  12: /// </summary>
  13: private void ApplyScale()
  14: {
  15:     ((CompositeTransform)ImgZoom.RenderTransform).ScaleX = TotalImageScale;
  16:     ((CompositeTransform)ImgZoom.RenderTransform).ScaleY = TotalImageScale;
  17: }

      上述中CompositeTransform.ScaleX/ScaleY、CompositeTransform.TranslateX/TranslateY,該CompositeTransform是什麼呢?

CompositeTransform:提供一個整合的對象,讓開發人員可以直接設定物件對應的Transform對象;

 

(3) 識別新的Scale與Translate是否符合圖像的限制:IsScaleValid()與IsDragValid()

   1: /// <summary>
   2: /// Checks that dragging by the given amount won't result in empty space around the image
   3: /// </summary>
   4: private bool IsDragValid(double scaleDelta, Point translateDelta)
   5: {
   6:     if (ImagePosition.X + translateDelta.X > 0 || ImagePosition.Y + translateDelta.Y > 0)
   7:         return false;
   8:  
   9:     if ((ImgZoom.ActualWidth * TotalImageScale * scaleDelta) + (ImagePosition.X + translateDelta.X) < ImgZoom.ActualWidth)
  10:         return false;
  11:  
  12:     if ((ImgZoom.ActualHeight * TotalImageScale * scaleDelta) + (ImagePosition.Y + translateDelta.Y) < ImgZoom.ActualHeight)
  13:         return false;
  14:  
  15:     return true;
  16: }
  17:  
  18: /// <summary>
  19: /// Tells if the scaling is inside the desired range
  20: /// </summary>
  21: private bool IsScaleValid(double scaleDelta)
  22: {
  23:     //比對 "總Image Scale * 移動新的Scale 是否有大於1 ,而且 
  24:     //比對 "總Image Scale * 移動新的Scale 是否有大於1 ,小於等於最大Scale比率
  25:     return (TotalImageScale * scaleDelta >= 1) && (TotalImageScale * scaleDelta <= MAX_IMAGE_ZOOM);
  26: }

    

(4) 搭配PinchDelta()與DragDelta()的方式,進行計算並且變動Scale與Position:UpdateImageScale()與UpateImagePosition()

      例如:PinchDelta()改變Scale也配合調整了Position;DragDelta()改變Position;

   1: /// <summary>
   2: /// Updates the scaling factor by multiplying the delta.
   3: /// </summary>
   4: private void UpdateImageScale(double scaleFactor)
   5: {
   6:     TotalImageScale *= scaleFactor;
   7:     ApplyScale();
   8: }
   9:  
  10: /// <summary>
  11: /// Updates the image position by applying the delta.
  12: /// Checks that the image does not leave empty space around its edges.
  13: /// </summary>
  14: private void UpdateImagePosition(Point delta)
  15: {
  16:     var newPosition = new Point(ImagePosition.X + delta.X, ImagePosition.Y + delta.Y);
  17:  
  18:     if (newPosition.X > 0) newPosition.X = 0;
  19:     if (newPosition.Y > 0) newPosition.Y = 0;
  20:  
  21:     if ((ImgZoom.ActualWidth * TotalImageScale) + newPosition.X < ImgZoom.ActualWidth)
  22:         newPosition.X = ImgZoom.ActualWidth - (ImgZoom.ActualWidth * TotalImageScale);
  23:  
  24:     if ((ImgZoom.ActualHeight * TotalImageScale) + newPosition.Y < ImgZoom.ActualHeight)
  25:         newPosition.Y = ImgZoom.ActualHeight - (ImgZoom.ActualHeight * TotalImageScale);
  26:  
  27:     ImagePosition = newPosition;
  28:  
  29:     ApplyPosition();
  30: }

 

[範例程式]

[執行結果] (注意,圖像來源非本App與作者所有)

Screen CaptureScreen Capture (2)Screen Capture (3)

======

此篇文章主要針對<Windows Phone 7: correct pinch zoom in Silverlight>中我覺得必需要懂得的地方,加以說明。

需要實際的Sample Code可至該出處進行下載。由於對於圖像的處理、座標的控制不是非常熟悉,因此,希望

撰寫該篇做為筆記之外,也能幫助跟我一樣對座標與Scale處理不是非常熟悉的。如果有撰寫錯誤,也請大家

給予指導。謝謝。

 

References:

How to zoom in and zoom out Images in WP7?

MultiTouch Behavior for Windows Phone 7

MultiTouch Behavior: Update for Windows Phone 7 tools beta

Windows Phone 7: correct pinch zoom in Silverlight

Touch Gestures on Windows Phone (原理,必讀)

借花献佛 WP7 中图片查看,缩放实现

WP7 and the Phone Controls Toolkit

[.NET]UTF-8轉換BIG5

WP7 Pinch and Pan Zoom an Image

GestureHelperEventArgs.cs

Windows Phone, Silverlight and WPF Multi-Touch Manipulations

风云的银光志Silverlight4.0教程之使用CompositeTransform复合变形特效实现倒影

 

Dotblogs Tags: