HTML5 Canvas – toDataURL() support for Android devices – working Phonegap 2.2.0 Plugin

Update
Save HTML5 Canvas Image to Gallery – Phonegap Android plugin
I have a Phonegap Android plugin that helps to save HTML5 Canvas Image to the Gallery. See it here.

Back to the actual post,

I have been working on a Phonegap based Android app which involves the HTML5 Canvas. So this is what I had been trying for some time – get a png/jpeg image of the Canvas (its a Canvas paint app, something like this and I want to get an image of the drawing..) and then upload the image to Facebook on a user’s album. Let me tell you, it has been a heck of a task and around 15-20 days of restlessness.

The problem stood tall when I discovered that for Android 2.3 (in general Android < 4.0) devices the native HTML5 Canvas- toDataURL() function does not work, which would otherwise give a base64 encoded string  as image data which then can be used as a source for a HTML <img /> tag. It was working for 4.0 devices as I tested it on a Samsung Galaxy S3 and a Samsung Tab. Hence I Goggled a bit and found out various people had this issue before. So it seemed that older Android web-kits (a Phonegap app runs on the Android webview which is actually the webkit browser inside a native wrapper) does not support that native method of converting a Canvas to an image through toDataURL() generated base64 encoded strings. Guess what I had todo? I had to go for a custom Phonegap plugin as there was no other way. This is how I thought of implementing it – pass the canvas object from javascript side to the Phonegap plugin’s Java class and somehow get the base64 encoded string of the Canvas and return it back to javascript as the callback parameter. This is what the native toDataURL() method actually does – convert the Canvas to a base64 encoded image data string.

Not being a Java developer, I had to look for external help, that’s where I came across Ryan Gillespie’s plugin which he developed some time back for the very purpose that I was looking for. But the code was kinda old and is not compatible with Phonegap 2.2.0 (this is the version that I am using for development) as there have been numerous changes in the Phonegap’s plugin architecture. I had to do some modifications to the code – mainly on the Java side. I used Ryan’s Java code and modified it a little bit. I wrote the javascript interface of the plugin entirely. Overall, thanks to Ryan for doing the tough part and its been possible to achieve the task. I had the modified plugin running successfully on older Android’s with Phonegap 2.2.0 and returning a valid base64 encoded image string of the HTML5 Canvas.

Let’s quickly talk on the changes that I made. I will not discus on how to create custom Phonegap plugins in this post. For that you need to checkout the official documentation. It is better if you have some idea of creating custom plugins and what goes into creating a plugin, specifically the communication between JavaScript and Java.

As I said earlier I modified Ryan’s Java class – CanvasPlugin.java. Download the file and check out the source. You will notice that the main changes are the new format for the overridden execute() method and it returns a boolean type now, also your plugin class should now extend the CordovaPlugin superclass, the third major change being the way callbackContext object now calls the success and error methods and in a way calls the javascript interface after the execute() method has finished it’s execution. There’s once more important change that I made is inside the custom getImageData() method – Base64.encodeBase64() has now been deprecated for the android.util.Base64 class, instead use Base64.encodeToString() method. So, mostly these changes made the Java class compatible with Phonegap 2.2.0. Now let’s check out the new JavaScript interface for the plugin.

I did not use Ryan’s javascript file. I wrote a simple one myself. So get a copy of it first – CanvasPlugin.js. The logic is simple if you know how to write a javascript part of a Phonegap plugin. I am creating a canvasplugin method and exposing it to the global window object so that it is accessible across your code. The canvasplugin method takes two parameters – the HTML5 Canvas reference and the callback function reference which will be called once we have a result from Java side. Next important thing is calling the CanvasPlugin.java execute() method and passing it the right arguments. For that I would again suggest to go through the official documentation. You can modify the javascript code to make it better probably.

Note: The files are now on Github. Any changes to the code will be committed to the Github repo.

With both the files ready, let’s see how to use it and get it working. Firstly you have to add these two files to your Phonegap Android project. Add the CanvasPlugin.js file inside assets/www/ folder and provide a reference to it in your index.html file, like this

<script type="text/javascript" charset="utf-8" src="CanvasPlugin.js"></script>

Then create a directory inside your project’s src folder that matches the package name of CanvasPlugin.java class. For our case make a directory – /org/apache/cordova/plugin inside src and then paste CanvasPlugin.java inside it. If you change the package name, make sure to change the directory structure as well. The package name can be found at the top of CanvasPlugin.java file.

Next thing to do is to register the plugin in the config file – open res/xml/config.xml and then add the plugin details given below to the <plugins></plugins> section of the XML file. The name attribute is the Java class name and the value is the path of the class. This should match the package name.

<plugin name="CanvasPlugin" value="org.apache.cordova.plugin.CanvasPlugin"/>

And then call the plugin inside your javascript (your script.js file or so) code like this. (You can call this inside a button click handler or so),

var canvas = document.getElementById("myCanvas");
window.canvasplugin(canvas,function(val){
  document.getElementById("myImg").src = val.data;
});

where myCanvas is the ID of the HTML Canvas. The callback function receives the Base64 encode image string inside the data property of the returned value. Make sure to access it through val.data. Now you can use this as a source for an HTML image tag and you can get a preview image of your canvas. Here I am getting a Base64 encoded .png image string. If you need to change the mimeType, you can do that inside the CanvasPlugin.js file. Also you can pass that as a parameter to the javascript interface rather than hard code inside the CanvasPlugin.js file (which I did mistakenly). So pass it from your script.js to window.canvasplugin() call along with the canvas reference and the callback reference, as the third parameter.

Lot of writing and English!! DOH!! But the cool plugin architecture has been able to overcome the shortcomings isn’t it.

About these ads

66 thoughts on “HTML5 Canvas – toDataURL() support for Android devices – working Phonegap 2.2.0 Plugin

  1. This is the javascript side of the plugin, it works with the above. Thanks for a good article joseph.

    “use strict”;
    // Inspired by http://ryangillespie.com/phonegap.php
    window.canvasplugin = function(canvas, mimeType, successCallback, failCallback) {
    //console.log(“CanvasPlugin.js: custom toDataURL called”);
    var canvasProps = {
    “mimeType” : “image/png”,
    “xpos” : 0,
    “ypos” : 0,
    “width” : 0,
    “height” : 0,
    “screenWidth” : document.getElementsByTagName(‘html’)[0].scrollWidth
    };

    if (canvas.toString().indexOf(“HTMLCanvasElement”) != -1) {
    canvasProps.mimeType = mimeType;
    canvasProps.xpos = canvas.offsetLeft;
    canvasProps.ypos = canvas.offsetTop;
    canvasProps.width = canvas.width;
    canvasProps.height = canvas.height;
    }
    function success(args) {
    successCallback(args);
    }
    function fail(args) {
    failCallback(args);
    }
    return cordova.exec(function(args) {
    success(args);
    }, function(args) {
    fail(args);
    }, ‘CanvasPlugin’, ‘toDataURL’, [ canvasProps.mimeType, canvasProps.xpos,
    canvasProps.ypos, canvasProps.width, canvasProps.height,
    canvasProps.screenWidth ]);
    };

    • Just put an alert in your callback function and check if the data coming from the plugin is a base64 image data. Like this,
      window.canvasplugin(canvas,function(val){
      alert(val.data);
      dataString = val.data;
      });
      Check if you are getting a long string of numbers and characters in the alert box. Try this first and let me know.

    • Yes, that’s what. This is correct. You are getting the image data in the form of base64 encoded string. Now just put this in your image source.

      img.src = data.val;

      The image should display…. but make sure there is something drawn in your canvas so that you can see whatever is drawn. For eg. draw a rectangle in your canvas and then try this.

  2. this is my code with the canvas creation as well

    //Canvas creation
    function loadImages(sources, callback) {
    var images = {};
    var loadedImages = 0;
    var numImages = 0;
    // get num of sources
    for(var src in sources) {
    numImages++;
    }
    for(var src in sources) {
    images[src] = new Image();
    images[src].onload = function() {
    if(++loadedImages >= numImages) {
    callback(images);
    }
    };
    images[src].src = sources[src];
    }
    }
    var canvas = document.getElementById(‘myCanvas’);
    var context = canvas.getContext(‘2d’);

    var sources = {
    image_pic: ‘img/bullseye.png’,
    image_frame: ‘img/frame1b.png’
    };

    var img = new Image();
    var sourceX,sourceY,sourceWidth, sourceHeight,destX,destY,destWidth,destHeight;
    img.onload = function() {
    sourceX=this.width/2 – 434/2;
    sourceY=this.height/2 -455/2;
    sourceWidth=434;
    sourceHeight=455;
    destWidth = sourceWidth;
    destHeight = sourceHeight;
    destX = canvas.width / 2 – destWidth / 2;
    destY = canvas.height / 2 – destHeight / 2 – 50;
    alert(“sx “+sourceX+”sy “+sourceY+”sw “+sourceWidth+”sh “+sourceHeight+”dx “+destX+”dy “+destY+”dw “+destWidth+”dh “+destHeight);
    }
    img.src = ‘img/bullseye.png';

    loadImages(sources, function(images) {
    //context.drawImage(images.image_pic, 0, 0, 434, 455);
    context.drawImage(images.image_pic, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
    context.drawImage(images.image_frame, 0, 0, 434, 455);
    });

    var canvas = document.getElementById(“myCanvas”);
    window.canvasplugin(canvas,function(val){

    $(‘#myCanvas’).attr(“style”, “display:none”);
    alert(val.data);

    dataString = val.data;
    alert(‘dataString’);

    document.getElementById(“myImg”).src = val.data;
    });

    • No this works………I have tried this out myself. This should definitely work. You can do one thing, see my blog post and download the source files CanvasPlugin.java and CanvasPlugin.js. I have given the links. And then follow the instruction given in the blog post to install and use the plugin. Just try that once.

  3. guess it works for drawing only do you know any other way to to save canvas into android phone or another plugin guess this one won’t work for me, but thank you very much for replying and trying to help me out it is very much appreciated

  4. already use you js and java with the plugin in xml and inclusion of the js in the index file hmm ill try to create a new project to try it might have problems with other plugins

  5. well tried with this code on a new phonegap project

    image is next to thisend of image

    document.addEventListener(“deviceready”, function(){
    //Canvas creation
    var c=document.getElementById(“myCanvas”);
    var ctx=c.getContext(“2d”);
    ctx.fillStyle=”#FF0000″;
    ctx.fillRect(0,0,150,75);

    var canvas = document.getElementById(“myCanvas”);
    window.canvasplugin(canvas,function(val){
    document.getElementById(“myImg”).src = val.data;
    });

    }, false);

    still a gray image is made and canvas has a red box

    im running a cherry mobile flare 4.0 ICS phone and cordova 2.4.0

    think i am doing something wrong but i followed you instructions got the js and java with added plugin in xml and js in index

  6. Pingback: android phonegap save canvas as base64 compatible with lower os - How-To Video

  7. was looking at the code and have a question how is the canvas data passed into the CanvasPlugin.java since what i saw that was passed as a json object was only position, height and width

    • We only pass the x,y, width and height. The Java class takes a screenshot of the region and converts it to a base64 encoded image data string. That string is then passed back to the javascript side.

    • We pass the data for the canvas like this, this is inside the CanvasPlugin.js file
      var canvasProps = {
      mimeType: “image/png”,
      xpos: canvasEl.offsetLeft,
      ypos: canvasEl.offsetTop,
      width: canvasEl.width,
      height: canvasEl.height,
      screenWidth: window.innerWidth // no WebView.getContentWidth(), use this instead
      };

      Then on the Java side we do this to read the values, this is inside CanvasPlugin.java file
      mimeType = data.getString(0);
      xpos = data.getInt(1);
      ypos = data.getInt(2);
      width = data.getInt(3);
      height = data.getInt(4);
      screenWidth = data.getInt(5);

  8. well then my problem is that it is not getting the screenshot i believe just asking then this code bmp = Bitmap.createBitmap(scaledViewWidth, scaledViewHeight, Bitmap.Config.RGB_565); should be the whole screen?
    tried to get that but it returns a black screen

    • I am not a Java developer and will not be able to help you on that front. But I got this part from Ryan Gillespie’s plugin code. See the link in the blog post. Which version of Android are you checking? And which Phoengap version. I tested it yesterday on an Android 2.2 device with Phonegap – 2.2.0 and it was working.

  9. This looks great. I have a plugin for iOS that takes a canvas and saves it to the Photo Library (and now a temp file). It would be great to use this as a base for extending it to support Android… What is the licensing on yours and Ryan’s work?

    • Hi Tommy-Carlos-Williams,
      I was the one who recently tumbled upon your plugin from Github and had an issue raised regarding getting the path of the saved image :) Good to see you here. As far my licensing goes, you can use the code and modify it. Ryan’s license says the same.
      Thanks.
      Joseph

  10. I tried this on Cordova 2.5 and for val.data I keep getting “undefined” does anyone know what this could be from? I’m testing on android 2.3

    added this to config.xml which is where my mainActivity.java is located

    also included the JS file before the code that calls the window.canvasplugin, this is also how im calling the plugin
    window.canvasplugin(oCanvas, function(val){
    alert(val.data);
    });

    • Josh,
      I have not tried this with Phonegap 2.5. The plugin architecture might be different from Phonegap 2.2, you may want to check that first as you never know. As for the plugin to work, it has been confirmed that it works with Phonegap 2.4. Also to give you an idea of debugging, put log messages(System.out.print…) inside the Java part of the plugin. In that way you will know what it is executing inside the java code. Print the values and see.

    • Yes, you have to set the image MIME type in the plugin’s javascript code (the .js file). Also, see the Java code (plugin’s .java file), you can see some System.out.print statements inside. And if you are using Eclipse IDE for development, then you can see those logs in the console.

  11. Can you create a GitHub repository for this? I have developed an PhoneGap plugin for sharing the content of a canvas to Instagram and this would be of great help for fixing the Android 2.x-3.x shortcomings. Also as @gevgeeks mentioned already we need a more transparent statement on licensing.

    [1] https://github.com/vstirbu/InstagramPlugin

  12. Hi there. Thanks for the help!! I’m having trouble trying to use your version of the plugin. I’m using Cordova 2.0.0 and I get trouble with this 2 imports:
    import org.apache.cordova.api.CallbackContext;
    import org.apache.cordova.api.CordovaPlugin;
    What should I do? I’m working on a app that applies a filter in an image and then I’m trying to save the canvas on the file system. Thanks!!

  13. I tried whith cordova 2.5 & cordova 2.6. It works but I get a screenshot (something like “printscreen”) in the data instead of my canvas content. The width & height are correct (my canvas’ size).

    • Ok. That seems strange. Did you pass the canvas instance correctly. I am asking you once again, although you mentioned it. Also there might be something wrong on the Java side. Paste your code here and I will look into it.
      Thanks

  14. here’s the code :

    photoChargee = function (image) {
    unCanvas = document.createElement("canvas");
    unCanvas.width = image.width;
    unCanvas.height = image.height;
    unCanvas.getContext("2d").drawImage(image,0,0,image.width,image.height);

    if (typeof(device) != "undefined") {
    console.log("mémorisation photo mobile "+image.src);
    window.canvasplugin(unCanvas,function(val){
    analyse.photosReduites.push(val.data);
    document.getElementById("loadedImage").src = val.data;
    }
    }
    }

    Thanks for your help.

  15. I simplified the application to check the problem. Here’s the link for the complete index1.html code : http://pierreandreline.free.fr/index1.html
    I use cordova 2.6 and I test the application on a samsung S5830 mobile with Android 2.3.
    the problem : window.canvasplugin(unCanvas,function(val) … when i put val.data in an img source, i get the screen picture, instead of the unCanvas content. I use the canvasplugin as an alternative when the toDataURL() does not work with old android versions.
    Thanks for your help.

  16. Pingback: Save HTML5 Canvas Image to Gallery – Phonegap Android plugin | RIA Lab

  17. Hi.,
    Its works fine., but in my app screen events freeze some ms and then works normal., is canvas freezes jquery methods?

    Thanks

  18. I have a div with a canvas element. Could I have that saved instead of a screenshot? I see how people here (pal) with the same issue. Has a fix been found?

    • Yes, you can save the div. You need to pass the div element object to the plugin. And about the issue, I have not got much time to have a look at it or fix it. But last time when I ran it on an Android 4 (Samsung S3) device, it ran without any issues.

  19. Pingback: Save HTML5 Canvas Image to Gallery - Phonegap Android plugin | PomPom Labs

  20. 09-12 23:08:24.133: E/Web Console(352): TypeError: Result of expression ‘window.plugins’ [undefined] is not an object. at file:///android_asset/www/index.html:72
    09-12 23:08:24.133: E/Web Console(352): TypeError: Result of expression ‘window.plugins’ [undefined] is not an object. at file:///android_asset/www/index.html:72

    jQuery Mobile

    document.addEventListener(“deviceready”, onDeviceReady, false);

    function onDeviceReady(){
    function renk_siyah_mi(deger1,deger2,deger3){
    a=200;
    if(deger1<a && deger2<a && deger3<a){
    don=1;
    }else{
    don=0;
    }
    return don;
    }
    function daire_siyah_mi(i,len,cwidth){
    deger1=0;
    deger2=0;
    bas=-3;
    son=3;

    for(t=bas;t<son;t+=1){
    for(j=bas;j0 && yer<len){
    if(renk_siyah_mi(resim_orj[yer],resim_orj[yer + 1],resim_orj[yer + 2])==1){
    deger1+=1;
    //dizi[yer]=1;
    }
    deger2+=1;
    }
    /* else{
    deger=-1;
    }*/
    }
    //document.writeln(" ");
    }

    if(deger1==deger2){
    don=1;
    for(t=bas;t<son;t+=1){
    for(j=bas;j<son;j+=1){
    yer=i+t*cwidth*4+j*4;
    dizi[yer]=1;
    }
    }
    }else{
    don=0;
    }
    return don;
    }

    var img = document.getElementById("scream");

    window.plugins.canvas.toDataURL(img, "image/png", success, failure);

    var canvas = document.getElementById("myCanvas");
    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");

    var c_durum1 = document.getElementById("myCanvas_durum1");
    var ctx_durum1 = c_durum1.getContext("2d");

    ctx.drawImage(img, 0, 0);
    var imgData = ctx.getImageData(0, 0, c.width, c.height);

    ctx_durum1.drawImage(img, 0, 0);
    var imgData_durum1 = ctx_durum1.getImageData(0, 0, c_durum1.width, c_durum1.height);

    var dizi = new Array();
    var resim_orj = new Array();
    //alert(imgData.data.length);
    for (var t = 0; t < imgData.data.length; t += 1) {
    resim_orj[t]=imgData.data[t];
    dizi[t]=0;
    }

    for (var i = 0; i < imgData.data.length; i += 4) {
    imgData.data[i] = 255 – imgData.data[i];
    imgData.data[i + 1] = 255 – imgData.data[i + 1];
    imgData.data[i + 2] = 255 – imgData.data[i + 2];
    imgData.data[i + 3] = 255;
    }
    for (var j = 0; j < imgData.data.length; j += 4) {
    if(daire_siyah_mi(j,imgData.data.length,c.width)==1){
    //dizi[j]=1;
    }
    }
    for (var k = 0; k < imgData_durum1.data.length; k += 4) {
    if(dizi[k]==1){
    imgData_durum1.data[k]=0;
    imgData_durum1.data[k+1]=255;
    imgData_durum1.data[k+2]=125;
    imgData_durum1.data[k+3]=255;
    }else{
    imgData_durum1.data[k]=255;
    imgData_durum1.data[k+1]=255;
    imgData_durum1.data[k+2]=255;
    imgData_durum1.data[k+3]=255;
    }
    }
    ctx.putImageData(imgData, 0, 0);
    ctx_durum1.putImageData(imgData_durum1, 0, 0);
    };

    Your browser does not support the HTML5 canvas tag.

    Your browser does not support the HTML5 canvas tag.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s