Windows Phone - Deep Zoom概要

Windows Phone - Deep Zoom概要

剛好最近比較有在處理圖像的效果,例如:包括配合手勢、Zooming/out、平移(Panning)等,所以一直在找相關

處理圖像的機制,發現Silverlight有提供一個好東西Deep Zoom,自己也沒有用過,所以就寫該篇來記錄一下。

 

Deep Zoom

     該元件提供高品質解析圖像(high-resolution image)的功能,讓User可Zoom in/out圖像且不影響Application本身的效能,

並且支援Smooth的載入、多張圖像載入與操作等功能。

 

1. Deep Zoom主要用在那些情境呢

     ‧Exploration of Very Large or High Resolution Images

           常見的情境就是用於瀏覽大型或高品質的圖像、地圖,例如:透過手指移動、Zoom in/out…等。

     ‧3-D Photography

            建立一個3D外型的圖像瀏覽清單,把所有的圖像組合成一個個小格式,並且集合於一個Room。讓用戶選擇格式,

            顯示圖像內容。

     ‧Advertisements

            可建立一個相對低品質的圖像載入,代表總主題的廣告。再逐一載入比較高品質的圖像顯示產品的廣告內容。

            透過逐步載入的方式可以讓原有的Application或Web不會造成效能的影響,可以在廣告不同的區域加上Zoom的效果。

          

2. 有關Deep Zoom的特性

     在使用Facebook或比較知名的App時,總是會想說為什麼它們載圖的時候,一開始出現的圖解析度很差,接著慢慢的變到原有解析度。

這樣的效果為的是加快初始化載入的速度,再慢慢改載入,常見的使用也包括:Zooming與Panning效果的呈現。Deep Zoom也採用這樣的

體驗效果,如下圖示:

     Preview different resolutions over time. 

透過spring animation效果讓用戶感覺圖示更在平順地改變,因此,當用戶Zooming/Panning時,即啟動該效果讓圖像不會

呈現忽然改變的樣式。

 

3. 如何建立一個Deep Zoom Image

    大略了解了Deep Zoom元件的特性與使用情境後,接下來先小試學習建立一個Deep Zoom的檔案。有幾個觀念要注意:

    ‧在應用程式使用Deep Zoom之前,必需先建立好Deep Zoom Image;

    ‧該Deep Zoom Image是一個由Tile組合而成的,並且JPG與PNG圖像採用不同的解析度;

    ‧每一個Tile的Size預設為:256x256,隨著關注的範圍而有所變動;

    ‧每一個Tile被儲存在一個分割的檔案,每一個圖像解析度level被分開存在不同的資料夾;

    ‧使用Tile的機制可讓Deep Zoom不用整個圖像下載下來,只需載入目前Screen要呈現範圍的Tile;

 

關於Tile與Deep Zoom運作的機制,可透過下圖解釋:

     Image pyramid used by Deep Zoom.

上圖可以得知為什麼圖像在載入時一開始是解析度較低的小圖,隨著Zooming/Panning後而所有不同更細緻的呈現。

那怎麼把原始的圖轉成這種多Tile的方式呢?先下載Deep Zoom Composer工具後,透過它來幫我們進行圖像的處理

詳細介紹Deep Zoom Composer的可參考<Deep Zoom File Format Overview>,另外可以配合Photosynth工具可以建立成

360度或3個維度的呈現機制。

 

建立好了Deep Zoom Image檔案後,需一個載入Depp Zoom Object的元件,例如:MultiScaleImage來載入它。

MultiScaleImage

     該類別允許用戶開啟多個分辨度(multi-resolution)的圖像,而且透過它進行Zooming與Panning。它是Deep Zoom中最重要的元件。

預設使用者不能Zooming或Panning圖像,需透過開發者將MultiScaleImage的縮放與平移功能開發出來才能使用,並且預設情境下,

MultiScaleImage在圖像第一次載入時會自動放大,可透過UseSprings屬性來關閉。

 

(A) 配合屬性

屬性 說明
ViewportWidth 設定/取得圖像實際顯示面積的寬度。
ViewportOrigin 設定/取得圖像實際顯示的左上角座標。
UseSprings 設定/取得指示是否啟動MultiScaleImage的spring animations。
Source 設定/取得MultiScaleTileSource物件,以用於MultiScaleImage的資料來源。
ActualHeight 取得FrameworkElement呈現的高度。
ActualWidth 取得FrameworkElement呈現的寬度。
AllowDownloading 設定/取得指定該MultiScaleImage是否允許被小載。
AspectRatio 取得做為MultiScaleImage圖像來源的寬高比率,該值為:圖像的寬度除以高度。

上述使用是常被用到的部分,尤其是ViewportWidth與ViewportOrigin這二個主要是針對MultiScaleImage目前

顯示的範圍與過去圖像處理是比較不相同的,要特別注意。

 

(B) 配合方法/事件:

方法/事件 說明
ImageOpenFailed 發生於執行失敗第一次載入所需要的Tile與開啟這些Tile時的事件,發生該事件也代表所有的Tile都不會被打開。
ImageOpenSucceeded 發生於執行成功第一次載入所需要的Tile與開啟這些Tile時的事件。
CaptureMouse 設定開始監控Mouse在該UIElement上的事件。
ReleaseMouseCapture 移除對該UIElement上的Mouse事件監控。
ElementToLogicalPoint 取得相對從MultiScaleImage的logic point (值為:0~1)與像素座標點。
透過該方法取得目前MultiScaleImage要進行縮放與移動內容圖像的依據,再根據改的指定值進行圖像的改變。
ZoomAboutLogicalPoint 提供用戶可以指定放大或縮小MultiScaleImage中的一固定範圍(Point)。

上述事件與方法中,在下述的範例也會用到,要注意在於ElementToLogicalPoint與ZoomAboutLogicalPoint二者,

它們採用的都是圖像與MultiScaleImage中的相對logical Point與像素座標點進行運算,雖然其計算原理不是重點,

但在我們進行Zooming或Panning這二個屬性是我們最關鍵的參數來源。

 

[範例] (以下人像圖示由該本人與所屬公司所擁有)

0. 透過Deep Zoom Composer工具將顯示的圖片建立成一個多解析度的圖像包

     建立好檔案後,記得Export出來,檔案目錄如下,並且下列檔案複制至Windows Phone專案中;

captuer

      記得選擇Export options為:Export as a composition (single image)。

image

      這是輸出的內容物,注意dzc_output.xml該檔案記錄了所有該專案中用到的圖示,並記下圖像分成多個

      像素後的資料位置,如dzc_output_files裡的內容。

 

[建議]

由於圖像要跟著不同解析度分成多個檔案與相對應的Metadata資料,所有的檔案加起來的大小,老實說並不適合直接

儲存於手機中,這樣會造成XAP檔過大。所以建議將這些檔案放至於網站上,透過動態載入的方式來使用

 

1. 在XAML中使用MultiScaleImage物件,將建立好的Deep Zoom Image加入,如下

     XAML檔定義:

   1: <Grid x:Name="LayoutRoot" Background="Transparent">
   2:     <Grid.RowDefinitions>
   3:         <RowDefinition Height="Auto"/>
   4:         <RowDefinition Height="*"/>
   5:     </Grid.RowDefinitions>
   6:     
   7:     <!--ContentPanel - place additional content here-->
   8:     <Grid x:Name="ContentPanel" Grid.Row="0" Margin="0,0,0,0">
   9:         <!-- 定義MultiScaleImage物件 -->
  10:         <MultiScaleImage x:Name="msi" Height="768" Width="481"
  11:                         VerticalAlignment="Top" HorizontalAlignment="Left" />
  12:     </Grid>
  13: </Grid>
  14:  
  15: <!-- 定義放大/縮小/恢復原狀的按鈕事件 -->
  16: <phone:PhoneApplicationPage.ApplicationBar>
  17: <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
  18:     <shell:ApplicationBarIconButton IconUri="/Images/appbar.minus.rest.png" Text="in" x:Name="btnIn" Click="btnIn_Click"/>
  19:     <shell:ApplicationBarIconButton IconUri="/Images/appbar.new.rest.png" Text="out" x:Name="btnOut" Click="btnOut_Click" />
  20:     <shell:ApplicationBarIconButton IconUri="/Images/appbar.check.rest.png" Text="home" x:Name="btnHome" Click="btnHome_Click" />           
  21: </shell:ApplicationBar>
  22: </phone:PhoneApplicationPage.ApplicationBar>

     加入Deep Zoom Image,並且註冊相關的事件,包括:ImageOpenSucceeded、MouseEvent。

   1: // Constructor
   2: public MainPage()
   3: {
   4:     InitializeComponent();
   5:     Init();
   6: }
   7:  
   8: private double zoom = 1;
   9: private bool duringDrag = false;
  10: private bool mouseDown = false;
  11: private Point lastMouseDownPos = new Point();
  12: private Point lastMousePos = new Point();
  13: private Point lastMouseViewPort = new Point();
  14:  
  15: private void Init()
  16: {
  17:     //註冊載入成功事件
  18:     msi.ImageOpenSucceeded += new RoutedEventHandler(msi_ImageOpenSucceeded);
  19:     //指定Source,將Source放置站台中
  20:     msi.Source = new DeepZoomImageTileSource(
  21:         new Uri("http://192.168.100.102/proj1/GeneratedImages/dzc_output.xml", UriKind.Absolute));
  22:  
  23:     //增加Panning的功能
  24:     this.MouseMove += new MouseEventHandler(MainPage_MouseMove);
  25:     this.MouseLeftButtonDown += new MouseButtonEventHandler(MainPage_MouseLeftButtonDown);
  26:     this.MouseLeftButtonUp += new MouseButtonEventHandler(MainPage_MouseLeftButtonUp);
  27: }
  28:  
  29: void msi_ImageOpenSucceeded(object sender, RoutedEventArgs e)
  30: {
  31:     //載入成功後,預設給予的座標與大小
  32:     Point tPoint = msi.ViewportOrigin;
  33:     msi.ViewportWidth = 1;
  34:     msi.ViewportOrigin = new Point(0, 0);
  35: }

 

2. 增加Panning/Zooming功能

     透過對Page本身註冊MouseMove、MouseLeftButtonDown與MouseLeftButtonUp三個事件,增加移動與Zoom的功能。

‧MouseLeftButtonDown

     擷取目前手指點擊的位置、手指點擊位置在MultiScaleImage中的位置,並且進行CaptureMouse()的事件。

   1: void MainPage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
   2: {
   3:     //擷取目前手指點擊的位置、手指點擊位置在MultiScaleImage中的位置
   4:     lastMouseDownPos = e.GetPosition(msi);
   5:     lastMouseViewPort = msi.ViewportOrigin;
   6:  
   7:     mouseDown = true;
   8:     //進行CaptureMouse()
   9:     msi.CaptureMouse();
  10: }

‧MouseMove

     MouseMove藉由LeftButtonDown取得的目前手指座標,隨著識別是否為Drag任務時,讓Viewport隨著Mouse與它的比例進行

     移動與Sprints效果的使用。

   1: void MainPage_MouseMove(object sender, MouseEventArgs e)
   2: {
   3:     lastMousePos = e.GetPosition(msi);
   4:     //識別是否點擊左鍵,並且還尚未進行Drag動作
   5:     if (mouseDown && !duringDrag)
   6:     {
   7:         //啟動Drag任務
   8:         duringDrag = true;
   9:         //取得目前ViewportWidth與座標
  10:         double w = msi.ViewportWidth;
  11:         Point o = new Point(msi.ViewportOrigin.X, msi.ViewportOrigin.Y);
  12:         //暫時關閉UserSprings效果,修改Viewport座標與Width
  13:         msi.UseSprings = false;
  14:         msi.ViewportOrigin = new Point(o.X, o.Y);
  15:         msi.ViewportWidth = w;
  16:         //記要縮放的比率
  17:         zoom = 1 / w;
  18:         //開啟UserSprings效果
  19:         msi.UseSprings = true;
  20:     }
  21:  
  22:     //識別如果啟動了Drag任務
  23:     if (duringDrag)
  24:     {
  25:         //取得Mouse左鍵按下去的坐標,進行移動的調整
  26:         Point newPoint = lastMouseViewPort;
  27:  
  28:         //X軸的移動比率為: (實際滑鼠移動的X - 實際在Viewport的X) / (MultiScaleImage實際的Width * MultiScaleImage View呈現的Width)
  29:         newPoint.X += (lastMouseDownPos.X - lastMousePos.X) / msi.ActualWidth * msi.ViewportWidth;
  30:         //Y軸的移動比率為: (實際滑鼠移動的Y - 實際在Viewport的Y) / (MultiScaleImage實際的Width * MultiScaleImage View呈現的Width)
  31:         newPoint.Y += (lastMouseDownPos.Y - lastMousePos.Y) / msi.ActualWidth * msi.ViewportWidth;
  32:  
  33:         //指定新的座標
  34:         msi.ViewportOrigin = newPoint;
  35:     }
  36: }

‧MouseLeftButtonUp

     MouseLeftButtonUp用由對MultiScaleImage中的Viewport進行Zoom與相對位置的移動,透過取得上次Zoom的大小進行調整,

     並且指定目前Viewport隨著Zoom後要移動的相對位置。

   1: void MainPage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     //識別如果不為Drag事件,只是單純的Click事件
   4:     if (!duringDrag)
   5:     {
   6:         //識別是否有按著Shift鍵,但手機裡沒有Shift鍵其實可以不用在意
   7:         bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
   8:         double newzoom = zoom;
   9:  
  10:         if (shiftDown)
  11:         {
  12:             newzoom /= 2;
  13:         }
  14:         else
  15:         {
  16:             //預設是最後一次更新的zoom比例 * 2
  17:             newzoom *= 2;
  18:         }
  19:         //呼叫Zoom方法,設定新的Zoom與相對移動的Point
  20:         Zoom(newzoom, msi.ElementToLogicalPoint(this.lastMousePos));
  21:     }
  22:     //關閉Drag與MouseDown的識別
  23:     duringDrag = false;
  24:     mouseDown = false;
  25:     //釋放擷取Mouse事件的方法。
  26:     msi.ReleaseMouseCapture();
  27: }

 

3. 增加Zooming功能

    在畫面中加了三個按鈕事件,Zooming in/out與恢復原狀的事件。

   1: private void Zoom(double pZoom, Point pPoint)
   2: {
   3:     if (pZoom < 0.5)
   4:     {
   5:         pZoom = 0.5;
   6:     }
   7:     //指定MultiScaleImage內要縮放的比例與相對移動位置
   8:     msi.ZoomAboutLogicalPoint(pZoom / zoom, pPoint.X, pPoint.Y);
   9:     zoom = pZoom;
  10: }
  11:  
  12: private void btnIn_Click(object sender, EventArgs e)
  13: {
  14:     //呼叫Zoom方式,並指定要拉進原Zoom Level的1.3倍,並且移動相對位置至新的座標
  15:     Zoom(zoom / 1.3, msi.ElementToLogicalPoint(new Point(.5 * msi.ActualWidth, .5 * msi.ActualHeight)));
  16: }
  17:  
  18: private void btnOut_Click(object sender, EventArgs e)
  19: {
  20:     //呼叫Zoom方式,並指定要拉遠原Zoom Level的1.3倍,並且移動相對位置至新的座標
  21:     Zoom(zoom * 1.3, msi.ElementToLogicalPoint(new Point(.5 * msi.ActualWidth, .5 * msi.ActualHeight)));
  22: }
  23:  
  24: private void btnHome_Click(object sender, EventArgs e)
  25: {
  26:     this.msi.ViewportWidth = 1;
  27:     this.msi.ViewportOrigin = new Point(0, -0.3);
  28:     zoom = 1;
  29: }

 

4. 執行畫面

000102

 

[範例程式]

(sample code: http://sdrv.ms/NdV5EB)

======

上述有些內容是參考MSDN與<一起学Windows phone 7开发四-(DeepZoom)>一文,介紹Deep Zoom與Deep Zoom Composer

如何使用於Windows Phone上。該元件有助於撰寫一些圖像的App與應用,可以省掉開發過程中透過某些手勢原理與相對

比率計算的成本,一樣可以達成需要的效果,可以參考看看。

 

References:

Deep Zoom on WP7

DeepZoomContainer, Expanded DeepZoom for Silverlight & Windows Phone 7 Series

一起学Windows phone 7开发四-(DeepZoom)

Windows Phone 7 series will also support DeepZoom

Windows Phone 7 不温不火学习之【DeepZoom 详细使用方法】 & 视频:Windows Phone 7实机演示:DeepZoom示例程序

Designing the Windows Phone 7 “Mix Zoomery” app

DeepZoom on Windows Phone 7

实例解析Windows Phone开发中DeepZoom功能

 

Dotblogs Tags: