Reputation: 11318
I need to create a thumbnail of an image on WP8, and currently I'm facing difficulties. In a nutshell, I know the only one way of doing this, that is using classes System.Windows.Controls.Image
, System.Windows.Media.Imaging.BitmapImage
and System.Windows.Media.Imaging.WritableBitmap
. I'm also trying to perform thumbnail creation on the threadpool, because it's a part of other bigger operation, which is running on a threadpool.
As you have probably already understood, I'm failing with invalid cross-thread access, even when I'm trying to create an instance of the above classes. That's a shame, really, because this thumbnail even is not going to be used in the UI, only saved to a file, and displayed from the file later on. My work has nothing to do with UI thread, and I'm still facing this limitations.
So is there some other way of creating the thumbnail from an image stream (I'm getting it from PhotoChooser task)? Maybe some other API, which doesn't require those UI-bound classes? Tried to bing it, even to google it, but with no luck.
Upvotes: 2
Views: 2925
Reputation: 11318
Okay, I think I'll put my own answer here as well, since it shows things from a bit of different perspective. The answer by Justin Angel is okay, but there's a couple of issues with it:
With this requirements in mind, here's my solution:
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;
}
I'm capturing UI thread's SynchronizationContext while I'm still in the UI thread (in View Model), and passing it further, and then I'm using closures to capture local variables, so that they're available for the callback which runs on UI thread. I'm also using lock and Monitor to synchronize these two threads and wait until the image is ready.
I'll accept my or Justin Angel's answer based on votes, if any. :)
EDIT: You can get an instance of Dispatcher's SynchronizationContext
through System.Threading.SynchronizationContext.Current
while you're on UI thread (in an button click handler, for example). Like this:
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);
}
Upvotes: 5
Reputation: 16102
Yeah, using WriteableBitmap requires access to the UI string. You might have to schedule the work on the UI thread as part of your workflow using the Dispatcher class.
The only other thing I can think off is saving the image to the Phone's MediaLibrary and using the Picture.GetThumbnail() method to get a very low-res thumbnail. It might or might not work without access to the UI thread. Also, once you add pictures to the user's MediaLibrary, you can't delete those so be careful not to spam those folders.
Upvotes: 2