2013-02-13 99 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中,您将无法删除这些图片,因此请注意不要将这些文件夹垃圾邮件。