Once we’ve got the basics of writing Windows Phone HTML5 apps, we’re probably going to want to start interacting with some of the cooler aspects of the phone. And first on my list is the camera.
In this tutorial, I’m going to walk though:
- Launching the native Windows Phone camera from an HTML5 app
- Returning the selected image to the browser hosting our app
- Displaying that image in the UI
Download the completed project
This app is pretty simple. We’re going to have a button to launch the photo chooser, which gives us the option to either take a new picture or select from images we’ve already saved. Then we’ll return that image to our app and show a thumbnail of it.
First let’s look at our basic HTML. This is the basic Windows Phone HTML app template from Visual Studio 2012.
In the WebBrowser XAML, I added an event handler for the ScriptNotify event and changed IsScriptEnabled to “True”.
<phone:WebBrowser x:Name="Browser" IsScriptEnabled="True" ScriptNotify="Catch_Script_Notification" />
Now for our HTML UI. Based on the description above, we’re just going to toss our images into a div as we get them. So our extremely simple HTML is…
<div> <button onclick="launchPhoto();">get new photo</button> </div> <div id="page-title"> <h2>add photos</h2> <div id="imageContainer"> </div> </div>
Note: I discarded the original phone.css because I felt it was too barebones. Instead, I’m using the file “ui-light.css”, which is the default css for the light theme in Windows 8 HTML/JS apps. As you would expect, it has some great styling for touch-enabled interfaces while fitting inside the modern *cough*-metro-*cough* design concepts. It can be found at:
Program Files (x86)\Microsoft SDKs\Windows\v8.0\ExtensionSDKs\Microsoft.WinJS.1.0\1.0\DesignTime\CommonConfiguration\Neutral\Microsoft.WinJS.1.0\css
Because you’re a quick study, you know that we need a “launchPhoto” function so that something actually happens when we press the button. So we’ll add that Javascript at the bottom of our file:
<script type="text/javascript"> function launchPhoto() { window.external.notify("LaunchPhotoChooser"); } </script>
This sends a message to the C# element of our application, where we’ll launch our native camera app to get the image. So we go to our event handler Catch_Script_Notification and add the following code:
PhotoChooserTask _photoChooser; private void Catch_Script_Notification(object sender, NotifyEventArgs e) { if (e.Value.StartsWith("LaunchPhotoChooser")) { _photoChooser = new PhotoChooserTask(); _photoChooser.Completed += _photoChooser_Completed; // allow the user to either choose a saved image or take a new one _photoChooser.ShowCamera = true; _photoChooser.PixelHeight = 260; _photoChooser.PixelWidth = 200; _photoChooser.Show(); } }
What are we doing here? We’ve defined a PhotoChooserTask, set up an event handler to listened for when it completes, set it up to give the user of taking an image or choosing an existing image, set some target image dimensions and finally launched it. We’re giving the image some defined pixel dimensions for the purpose of illustration. It isn’t strictly necessary.
Now we’re in the PhotoChooserTask UI. The camera icon at the bottom of the screen takes the user to the standard camera to take a new picture.
Once we have the image, we’re taken to a UI for selecting the dimension we specified.
And then we head back to our app with a stream holding the image we just took or selected. We’ll return to the app in the _photoChooser_Completed event.
void _photoChooser_Completed(object sender, PhotoResult e) { // Get a byte[] from the stream byte[] photoArray = new byte[e.ChosenPhoto.Length]; e.ChosenPhoto.Position = 0; e.ChosenPhoto.Read(photoArray, 0, photoArray.Length); // turn the byte[] into a string string photoString = Convert.ToBase64String(photoArray); Deployment.Current.Dispatcher.BeginInvoke(() => { // add "data:image/png;base64" to the image header and // go back to a JS function with some dimension information string[] parameters = new string[] { "data:image/png;base64," + photoString, "260", "200" }; Browser.InvokeScript("setPhotoWithDimensions", parameters); }); }
OK… what just happened here?
First, the way we communicate with the Javascript elements in our app is by invoking Javascript functions using the Browser.InvokeScript method. Is we want to pass in a parameter to a script, we need to pass it in as a string in a string array (which will hold all the parameters so we can run functions that require any number of parameters). But since we can only pass in strings, we need to turn the image into a string.
Fortunately, this isn’t very hard. We simply get the the data out of our image stream (e.ChosenPhoto) and put it in a byte array. From there, we can just convert it to a base64 string and add the header
<span style="background: white; color: #a31515;">"data:image/png;base64,"</span>
And the pixel dimensions of our source image. We send all the info to the HTML/JS part of our app with the InvokeScript method, so we’ll want to make sure we can handle that function appropriately.
So let’s go back to our Javascript and add that function.
function setPhotoWithDimensions(photoString, w, h) { var width = parseFloat(w); var height = parseFloat(h); var newImg = document.createElement('img'); newImg.src = photoString; if (width > height) { newImg.style.cssText = 'width:130px; height=auto;'; } else { newImg.style.cssText = 'height:130px; width=auto;'; } var imageDiv = document.createElement('div'); imageDiv.appendChild(newImg); var imageContain = document.getElementById('imageContainer'); imageContain.appendChild(imageDiv); }
The Javascript side of this equation is actually pretty simple. We parse our image dimensions, create an img element and dump our image string into it, set the appropriate style (depending on the dimensions… we don’t want our image to get too small or too big).
Then we create a new div, put our img into it and add that div to our waiting image container. And we’re all done.
And if we like, we can just add another image to our div.
And on and on until we’re bored.
Hopefully this has been a helpful example of inter-op between Windows Phone HTML apps and the native camera functionality. Keep an eye on this space… I’m working on lots of examples of these kinds of go-between solutions that make HTML apps in Windows Phone a little easier.