DEVELOPERS BLOG

Nobody Puts Filtermama in a Corner

HTML5 / 01.23.14 / Chad Tetreault

filtermama

Although I’m primarily a web developer, over the past few months I’ve been working outside of my comfort zone by hacking and slashing my way through C++ and Cascades. Overall it’s been a great experience, and I’ve come to realize something important: the future of mobile development isn’t “native vs. web”, it’s hybrid apps; combining the flexibility of JavaScript with the power of Cascades into a high-performance application.

So, once I knew how I wanted to build an app, it was time to figure out what to build. I’ve always had a love of photography, and over the past few years, camera apps as well. Naturally, I decided to build something ground breaking: an app which (wait for it…) added vintage filters to images!

By combining my JavaScript and HTML skills, with my new-found knowledge of Cascades and a bit of C++, I was able to build an app that combined the best of both worlds: Filtermama.

How hybrid apps work

This blog post assumes that you already have a basic understanding of how Hybrid apps work on the BlackBerry 10 platform.

My teammate, Anzor Bashkhaz, has written an extensive blog series that covers everything you need to know to get started. Rather than duplicate his efforts, I encourage you to check out his post on the BlackBerry DevBlog, titled Hybrid Apps for BlackBerry 10, for more information.

Built for BlackBerry

To get started quickly on the project I used the Built for BlackBerry Boilerplate template for Cascades. It’s open-source and available for download in the BlackBerry Cascades-Samples GitHub repo. Out-of-the-box you get a blank app that has all the components needed to qualify for Built for BlackBerry.

Conception

The concept behind Filtermama is basic…

  • User takes a photo
  • User selects desired filter
  • User shares, or saves the filtered photo

Let’s take a look at how each step was accomplished.

Capturing an image

To let the user select a photo, the FilePicker control is being used in QML. You might ask why I didn’t use the camera card.Great question. Simple answer is: I’m lazy.

The FilePicker lets us make use of the imageCropproperty. Once the user selects an image or takes a photo, the photo editor card is loaded and the user can crop their photo. That is important because I really didn’t want to deal with detecting different image sizes, aspect ratios, and orientations.

That being said, if I were to redo that logic in the future, I would give the user more control over their picked photo, allowing them to rotate it, crop it (if they want to), and so on. Live and learn, I guess. 🙂

Let’s take a look under the hood, and see how Filtermama runs.

Native side

Here’s a quick look at the FilePicker QML. Note: the imageCropEnabled property.

// file: BasePage.qml  
FilePicker {     
   id: picker     
   property string pickedPhoto     
   title: "Pick a photo"     
   type: FileType.Picture     
   imageCropEnabled: true     
   onFileSelected: {}     
   onCanceled: {} 
}

Once the file is picked and cropped, it’s prepared to be added to the app. This is where the web layer comes in to play. To prepare the photo, Canvas is used. The photo is resized, and added to the html document.

This is where the magic first happens. To talk to the web layer from Cascades we use the postMessage function.

// file: Filtering.qml  
   var msg = {     
      'cmd': 'loadPhoto',     
      'data': imgData 
   };  

   var message = JSON.stringify(msg); webview.postMessage(message); 
Web Side

In the webview, Filtermama is listening for commands from Cascades via navigator.cascades.onmessage.

// file: index.js  
navigator.cascades.onmessage = function onmessage(message) {     
   message = JSON.parse(message);      
   // load photo     
   if (message.cmd === 'loadPhoto') {         
      thePhoto.src = message.data;         
      prepareImage();     
   } 
}; 

This back-and-forth communication is the core of how the hybrid app works. Again, check out Anzor’s hybrid app blog series if I’ve lost you. 🙂

Filtering

One of the biggest factors for handling the filtering in web is that Canvas is absolutely awesome to work with. There are many libraries out there to do this exact thing, and I’ve tested out most of them.

For Filtermama, I decided to go with Filter.me by Matthey Ruddy. It’s dual licensed under the MIT and GPL licenses, coded cleanly, and is optimized for performance.

Native side

To apply a filter we simply post a message to the webview that contains the filter’s settings. These settings all originate from a JSON fileassets/filters/filters.json.

// file: Filtering.qml  
var msg = {     
   'cmd': 'filter',     
   'data': filterData 
};  
var message = JSON.stringify(msg); webview.postMessage(message); 
Web side

In the webview the filter data is parsed, and passed to Filter.me, which performs all the hard work for us.

Here’s a sample of what an actual filter object looks like, from the filters.json file.

// file: filters.json 
{
   "id": "1980",     
   "type": "default",     
   "title": "1980",     
   "thumbnail": "filters/1980.png",     
   "filter": {         
      "curves": {             
         "a": "[alpha channel values]",             
         "r": "[red channel values]",             
         "g": "[gren channel values]",             
         "b": "[blue channel values]"         
      },         
      "desaturate": false,         
      "vignette": false     
   } 
} 

The actual call to the Filter.me library is super easy because it is making use of jQuery (shh, don’t hate). As you can see, an effect object (which contains the JSON data) called filter is passed to Filter.me, and the rest is handled by the library.

// file: index.js  
var process = function(effect) { 
   filterId = effect.id;  
   jQuery(document).ready(function($) {     
      $('#the-photo').filterMe(         
         filter     
      ); 
   });
} 
Native side

Once Filter.me is finished, a message is posted back to the native layer and received by the webview’s onMessageReceived listener, which in this case, tells Cascades to turn off the activity spinner and give focus back to the user.

// file: Filtering.qml  
onMessageReceived: {     
   if (data.message === 'filter-done') {         
      spinner.off();     
   } 
} 

Sharing, and Saving

Photo sharing and saving is handled by the native layer, but remember we’re filtering the image in the web layer so we need to get the Base64 data from the canvas into Cascades and C++. Since Filter.me is replacing the image source with a Base64 string, we simply grab that data, and post it to Cascades.

Web side
var savePhoto = function() {     
   var b64 = thePhoto.src;     
   navigator.cascades.postMessage(b64); 
}; 
Native side

Once Cascades receives the message, again via the onMessageReceived handler, it passes the Base64 string to C++ via app.savePhoto() which saves it.

data = data.replace('data:image/png;base64,', ''); 
savedFilePath = app.savePhoto(data); 

Now that the file is saved, and we have the savedFilePath, we detect if the user wants to share or just save the photo.

If they choose to save, a toast is displayed telling the user the photo is saved.

If the user wants to share the photo, we use the Invocation Framework to do so by passing it to the savedFilePath. A Share Card is displayed, and the user can now choose where they want their photo posted.

I used the Social Invocation sample app to add this functionality to the app. Below is a snippet from the shareFile() method.

C++
void ApplicationUI::shareFile(QString fileName) {   
   invocation = Invocation::create(   
      InvokeQuery::create().parent(this).uri(    
      QUrl::fromLocalFile(fileName)));   
      connect(invocation, SIGNAL(armed()), this, SLOT(onArmed()));            
      connect(invocation, SIGNAL(finished()), invocation, SLOT(deleteLater()));   
} 

Downloads

Filtermama has been open-sourced as “Filtermama Lite” under the Apache 2.0 license. Feel free to modify, and adapt it to meet your needs.

If you find it useful in any way, please show your support by following me on Twitter, and letting me know!

Check-out the latest version of Filtermama Lite on GitHub.

Reference

About Chad Tetreault

A developer at heart, I’ve been coding in some shape or form since the age of 13. Nowadays I focus on building awesome cross-platform (Android, iOS, BlackBerry) mobile web applications powered by Cordova, JavaScript, and AngularJS.

Chad Tetreault

About Chad Tetreault

A developer at heart, I’ve been coding in some shape or form since the age of 13. Nowadays I focus on building awesome cross-platform (Android, iOS, BlackBerry) mobile web applications powered by Cordova, JavaScript, and AngularJS.