2013-02-13 102 views
2

我需要在WP8上創建一個圖像的縮略圖,目前我正面臨困難。簡而言之,我知道使用類System.Windows.Controls.Image,System.Windows.Media.Imaging.BitmapImageSystem.Windows.Media.Imaging.WritableBitmap這樣做的唯一方法。我也試圖在線程池上執行縮略圖創建,因爲它是線程池上運行的其他更大操作的一部分。在線程池中創建Windows Phone 8圖像的縮略圖

正如你可能已經明白,即使我試圖創建上述類的實例,我無法跨線程訪問無效。真的很遺憾,因爲這個縮略圖甚至不會在UI中使用,只保存到一個文件中,並在以後的文件中顯示。我的工作與UI線程無關,而且我仍然面臨着這個限制。

那麼是否有其他方式從圖像流創建縮略圖(我從PhotoChooser任務中獲取它)?也許一些其他API,它不需要那些UI綁定的類?試圖給它,甚至谷歌它,但沒有運氣。

回答

5

好的,我想我也會在這裏提出自己的答案,因爲它從不同的角度展示了一些東西。由Justin天使答案是好的,但有幾個問題吧:

  1. 它不可能有調度的參考,當代碼在模型層深,在後臺線程上運行。
  2. 我需要從方法中返回縮略圖並稍後在相同的同步上下文中使用它。否則,我將不得不圍繞這種創建縮略圖的方法來改變很多代碼。

有了這個需求,這是我的解決方案:

private WriteableBitmap CreateThumbnail(Stream stream, int width, int height, SynchronizationContext uiThread) 
     { 
      // This hack comes from the problem that classes like BitmapImage, WritableBitmap, Image used here could 
      // only be created or accessed from the UI thread. And now this code called from the threadpool. To avoid 
      // cross-thread access exceptions, I dispatch the code back to the UI thread, waiting for it to complete 
      // using the Monitor and a lock object, and then return the value from the method. Quite hacky, but the only 
      // way to make this work currently. It's quite stupid that MS didn't provide any classes to do image 
      // processing on the non-UI threads. 
      WriteableBitmap result = null; 
      var waitHandle = new object(); 
      lock (waitHandle) 
      { 
       uiThread.Post(_ => 
       { 
        lock (waitHandle) 
        { 
         var bi = new BitmapImage(); 
         bi.SetSource(stream); 

         int w, h; 
         double ws = (double)width/bi.PixelWidth; 
         double hs = (double)height/bi.PixelHeight; 
         double scale = (ws > hs) ? ws : hs; 
         w = (int)(bi.PixelWidth * scale); 
         h = (int)(bi.PixelHeight * scale); 

         var im = new Image(); 
         im.Stretch = Stretch.UniformToFill; 
         im.Source = bi; 

         result = new WriteableBitmap(width, height); 
         var tr = new CompositeTransform(); 
         tr.CenterX = (ws > hs) ? 0 : (width - w)/2; 
         tr.CenterY = (ws < hs) ? 0 : (height - h)/2; 
         tr.ScaleX = scale; 
         tr.ScaleY = scale; 
         result.Render(im, tr); 
         result.Invalidate(); 
         Monitor.Pulse(waitHandle); 
        } 

       }, null); 

       Monitor.Wait(waitHandle); 
      } 
      return result; 
     } 

我捕捉UI線程的SynchronizationContext,而我仍然在UI線程(在視圖模型),並進一步傳遞,然後我使用閉包來捕獲局部變量,以便它們可用於在UI線程上運行的回調。我還使用鎖定和監視器來同步這兩個線程,並等待圖像準備就緒。

我會根據選票接受我或賈斯汀安琪的答案,如果有的話。 :)

編輯:您可以通過System.Threading.SynchronizationContext.Current得到Dispatcher的SynchronizationContext的實例,而你在UI線程(在按鈕單擊處理程序,例如)。像這樣:

private async void CreateThumbnailButton_Clicked(object sender, EventArgs args) 
    { 
     SynchronizationContext uiThread = SynchronizationContext.Current; 
     var result = await Task.Factory.StartNew<WriteableBitmap>(() => 
      { 
       Stream img = GetOriginalImage();// get the original image through a long synchronous operation 
       return CreateThumbnail(img, 163, 163, uiThread); 
      }); 
     await SaveThumbnailAsync(result); 
    } 
+0

Downvoters,小心解釋爲什麼?這是工作和確定性的方法。 – Haspemulator 2013-04-10 15:58:29

+3

好吧,既然我收到了另一個downvote,我再次問:這種方法究竟有什麼錯誤?爲什麼它不是一個好方法?如果你倒退了但不指出答案的缺點,這只是浪費你的時間(儘管我明白用鼠標點擊一下比寫一些有意義的事情容易)。 – Haspemulator 2013-04-19 11:04:41

+0

我如何通過ViewModel調用這個方法。從哪裏我可以得到SynchronizationContext。你可以添加調用方法的方式嗎? – StezPet 2013-04-19 11:31:47

2

是的,使用WriteableBitmap需要訪問UI字符串。您可能必須將UI線程中的工作安排爲使用Dispatcher類的工作流程的一部分。

我唯一能想到的另一件事是將圖像保存到手機的MediaLibrary,並使用Picture.GetThumbnail()方法獲取非常低分辨率的縮略圖。沒有訪問UI線程,它可能會或可能不會工作。另外,一旦將圖片添加到用戶的MediaLibrary中,您將無法刪除這些圖片,因此請注意不要將這些文件夾垃圾郵件。