For me, coming from a mostly web development background, threads are one of those things that I’m aware of and avoid at all costs. They seem complicated and they’re difficult to debug and they’re just yucky. They are also incredibly necessary if you want a responsive user interface during long running tasks (like manipulating every pixel in a 7 megapixel image multiple times and then saving it to disk.) Keep in mind, based on my background and lack of experience in this area, that anything I write below works for my app, but may in fact be an incredibly poor implementation that could be better optimized. If you see anything that looks stupid, I welcome the feedback so I can get better.
In Camera Hack I used two different threading methods. One used the actual System.Threading.Thread class, which was for the live camera preview feed. The second used the System.ComponentModel.BackgroundWorker to allow the user to see a progress bar for the manipulation of the final image while it’s processing and saving.
For the live camera preview, I’ll refer you again to the excellent samples on MSDN: http://msdn.microsoft.com/en-us/library/hh202982%28v=vs.92%29.aspx. It shows how to set up the function that your thread will be calling, how to start up the thread and how to use the Dispatcher.BeginInvoke function to pass information back to the main thread.
Using that sample as a starting point, everything was going great, and then I needed to save the image, which seemed like it was going fine, but it was hard to tell. I had a lovely status message that said “Saving image…” and eventually that went away and my image was saved and my live preview started back up. But there was no telling how long it was going to take or if it got hung up somewhere. And if I as the developer don’t know if the app has hung, no user is going to wait around. They’re gonna try to use the UI, then they’re gonna exit the app. And if the image hasn’t been saved by that time, they’re gonna assume it’s broken and uninstall. And if they’re really mad because that was a great picture they just lost, then they’re gonna leave a negative review and nobody wants that. So I needed a progress bar.
BackgroundWorkers are really cool for this because they have a handy method called ReportProgress which takes an int and provides good place to set the progress of your progress bar (I want to believe I used the word progress in that sentence enough times, but I really think it needed one more).
Declare the background worker in your page.
private BackgroundWorker backgroundWorker;
Wire up the events in your constructor.
backgroundWorker = new BackgroundWorker(); backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged); backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); backgroundWorker.WorkerReportsProgress = true;
On your camera capture event, use the Dispatcher.BeginInvoke to fire off the worker.
private void camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e) { Dispatcher.BeginInvoke(delegate() { var bitmap = new WriteableBitmap((int)cam.Resolution.Width, (int)cam.Resolution.Height); //Load the captured image stream to the bitmap bitmap.LoadJpeg(e.ImageStream); if (backgroundWorker.IsBusy != true) { backgroundWorker.RunWorkerAsync(bitmap); } }); }
The Do Work event is where we (surprise!) do work. It’s fairly straight forward, loop through all the pixels in the writable bitmap that was passed into the function and update them with the ProcessPixel function. You can get some idea of what I’m doing to the pixels and see my rbg class in my previous blog post. One non-obvious optimization I did here was in calling the ReportProgress every 10000 pixels. Doing the type conversions and division was costly enough that it was significantly slowing down the loop. Once the work is done, there is another Dispatcher.BeginInvoke (those things are everywhere) that pushes the finalized bitmap data to a file.
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { var worker = (BackgroundWorker)sender; var bitmap = (WriteableBitmap)(e.Argument); var inRBG = new rbg(); for (int i = 0; i < bitmap.Pixels.Length; i++) { inRBG.ConvertFromInt(bitmap.Pixels[i]); bitmap.Pixels[i] = ProcessPixel(inRBG); if (i % 10000 == 0) { worker.ReportProgress((int)(((decimal)i / (decimal)(bitmap.Pixels.Length)) * 100)); } } this.Dispatcher.BeginInvoke(delegate() { try { var stream = new MemoryStream(); bitmap.SaveJpeg(stream, bitmap.PixelWidth, bitmap.PixelHeight, 0, 100); //Take the stream back to its beginning because it will be read again //when saving to the library stream.Position = 0; //Save picture to device media library MediaLibrary library = new MediaLibrary(); string fileName = string.Format("{0:yyyy-MM-dd-HH-mm-ss}.jpg", DateTime.Now); library.SavePicture(fileName, stream); } catch (Exception) { } finally { InitializeFilteredDisplay(); } }); }
Here is that progress changed event I mentioned earlier, allowing the UI to update with the current percentage complete. (SavingProgress is the name of my ProgressBar.)
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { SavingProgress.Value = e.ProgressPercentage; MainImageTxt.Text = "Saving image... " + e.ProgressPercentage.ToString() + "%"; }
You can use the RunWorkerCompleted event as a handy way of letting the rest of the app know that this process is complete. I used it to let my main thread know it was OK to resume processing the live preview (which is turned off while an image is being saved) and to reset the progress bar to 0 for the next time that I use it.
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { capturingImage = false; if (camInit) { SavingProgress.Value = 0; } }
This code works pretty well overall. There are two items about the behavior of the app I don’t like. One is that the background worker can’t keep going and just finish saving the file if the user exits the app. You don’t want to prevent the user from leaving, because this is a phone and stopping them from taking a call is even worse than losing a picture. The other thing is that even though the Dispatch.BeginInvoke code in the DoWork function basically runs outside of the function, it doesn’t return immediately, so the progress bar sits at 99% for a couple seconds while the image actually saves. It’s not a terrible user experience, but it isn’t as polished as I had hoped for. The only good thing is that once it hits that 99%, it seems to be safe for the user to exit the app and the image will still successfully save.
1 comment