Phonegap Plugin:How to convert Base64 String to a PNG image in Android

19,850

Solution 1

The solution; This plugin that converts a Base64 PNG String and generates an image to the sdCard. Let's go!

1. The Base64 Decoder

Get this blazing fast Base64 encode/decoder class called MiGBase64. Download it from SourceForge. Create a folder called 'util' within your project's src/ folder. Place the downloaded class there.

2. The java

Create a folder called 'org/apache/cordova' within your project's src/ folder. Create in it a Java file called "Base64ToPNG.java" with the following source code.

package org.apache.cordova;

/**
* A phonegap plugin that converts a Base64 String to a PNG file.
*
* @author mcaesar
* @lincese MIT.
*/

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;

import android.os.Environment;
import java.io.*;
import org.json.JSONException;
import org.json.JSONObject;
import util.Base64;

public class Base64ToPNG extends Plugin {

    @Override
    public PluginResult execute(String action, JSONArray args, String callbackId) {

        if (!action.equals("saveImage")) {
            return new PluginResult(PluginResult.Status.INVALID_ACTION);
        }

        try {

            String b64String = "";
            if (b64String.startsWith("data:image")) {
                b64String = args.getString(0).substring(21);
            } else {
                b64String = args.getString(0);
            }
            JSONObject params = args.getJSONObject(1);

            //Optional parameter
            String filename = params.has("filename")
                    ? params.getString("filename")
                    : "b64Image_" + System.currentTimeMillis() + ".png";

            String folder = params.has("folder")
                    ? params.getString("folder")
                    : Environment.getExternalStorageDirectory() + "/Pictures";

            Boolean overwrite = params.has("overwrite") 
                    ? params.getBoolean("overwrite") 
                    : false;

            return this.saveImage(b64String, filename, folder, overwrite, callbackId);

        } catch (JSONException e) {

            e.printStackTrace();
            return new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());

        } catch (InterruptedException e) {
            e.printStackTrace();
            return new PluginResult(PluginResult.Status.ERROR, e.getMessage());
        }

    }

    private PluginResult saveImage(String b64String, String fileName, String dirName, Boolean overwrite, String callbackId) throws InterruptedException, JSONException {

        try {

            //Directory and File
            File dir = new File(dirName);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File file = new File(dirName, fileName);

            //Avoid overwriting a file
            if (!overwrite && file.exists()) {
                return new PluginResult(PluginResult.Status.OK, "File already exists!");
            }

            //Decode Base64 back to Binary format
            byte[] decodedBytes = Base64.decode(b64String.getBytes());

            //Save Binary file to phone
            file.createNewFile();
            FileOutputStream fOut = new FileOutputStream(file);
            fOut.write(decodedBytes);
            fOut.close();


            return new PluginResult(PluginResult.Status.OK, "Saved successfully!");

        } catch (FileNotFoundException e) {
            return new PluginResult(PluginResult.Status.ERROR, "File not Found!");
        } catch (IOException e) {
            return new PluginResult(PluginResult.Status.ERROR, e.getMessage());
        }

    }
}

3. The Javascript

Write this JavaScript as Base64ToPNG.js to your project's www folder. DONT forget to include a reference to it in your html files.

/**Works on all versions prior and including Cordova 1.6.1 
* by mcaesar
*  MIT license
*  
*/

(function() {
    /* This increases plugin compatibility */
    var cordovaRef = window.PhoneGap || window.Cordova || window.cordova; // old to new fallbacks

    /**
    * The Java to JavaScript Gateway 'magic' class 
    */
    function Base64ToPNG() { }

    /**
    * Save the base64 String as a PNG file to the user's Photo Library
    */
    Base64ToPNG.prototype.saveImage = function(b64String, params, win, fail) {
        cordovaRef.exec(win, fail, "Base64ToPNG", "saveImage", [b64String, params]);
    };

    cordovaRef.addConstructor(function() {
        if (!window.plugins) {
            window.plugins = {};
        }
        if (!window.plugins.base64ToPNG) {
            window.plugins.base64ToPNG = new Base64ToPNG();
        }
    });

})(); 

4. The plugins.xml file

Add the following to res/xml/plugins.xml file

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

5. Finally, HTML examples and the parameters

<button onclick="test();">No optional params required, Cowboy.</button> </br>
<button onclick="test2();">Make PNG with some parameters</button>

<script src="Base64ToPNG.js" type="text/javascript"></script>

<script type="text/javascript">

//May have a mime-type definition or not 
var myBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="//a red dot


function test(){

    //Illustrates how to use plugin with no optional parameters. Just the base64 Image.
    window.plugins.base64ToPNG.saveImage(myBase64, {}, 
       function(result) {
          alert(result);
       }, function(error) {
          alert(error);
       });
 }

 //No mimetype definition example
 var myOtherBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

 function test2(){

    //Shows how to use optional parameters
    window.plugins.base64ToPNG.saveImage(myBase64, {filename:"dot.png", overwrite: true}, 
       function(result) {
          alert(result);
       }, function(error) {
          alert(error);
    });

 }
 </script>

Parameters

  1. filename: Name of the file to be generated. By default the same as the one in url.
  2. folder: Name of the directory to generate the file to. By default "sdcard/Pictures"
  3. overwrite: If the file already exists, replace it. By default false.

    I hope this answers some bothering questions. Happy coding!

Solution 2

Fo anybody wanting to use this with kineticjs, the following works a treat:

function saveCanvas() {
    $('#save').bind( $bind, function(){
        stage.toDataURL({
        callback: function(dataUrl){
            window.plugins.base64ToPNG.saveImage(dataUrl.substr(22,dataUrl.length), {}, 
                function(result) {
                    alert(result);
                }, function(error) {
                    alert(error);
                }
            );
        },
            mimeType: 'image/png',
            quality: 0.5
        });
    });
}

Solution 3

This solution only works when feeding it a CLEAN Base64 string. In other words, the "data:image/png;base64," part should be removed or the Base64 decoder fill fail, causing a nullpointer error when writing the file.

Also I noticed that the image does not show up in the Gallery but it is stored correctly on the SD card. When I download it to my PC I can open it just fine. Not sure what that is about.

Thanks for the work!

Share:
19,850
mukama
Author by

mukama

I had to write something here to get a badge.

Updated on June 05, 2022

Comments

  • mukama
    mukama almost 2 years

    Android does not allow native apps like Phonegap-based apps to write binary files. A common application is converting Base64 Strings to Images. So, how do you go about this problem?

  • mukama
    mukama almost 12 years
    Thanks for the corrections. Edited the java to support full "data:image/png;base64". As for the picture not appearing in the gallery, android indexes media files only when storage drives change state eg from on to off.
  • Alston
    Alston over 11 years
    @mcaesar I have 3 questions about your plugin: 1. I found that the parameter "folder" should be: sdcard/somedirectory_name instead of "somedirectory_name". 2.I set a break point into the instrument: byte[] decodedBytes = Base64.decode(b64String.getBytes()); in Base64ToPNG.java. And I found the Base64.decode(~) returns a NULL. 3. In your description, you said "Place the downloaded class there.",but I only get the file: Base64.java. Is anything wrong? I really need your plugin to convert my canvas to .png file, saving it in the local system. Please give me some guidance.
  • Alston
    Alston over 11 years
    @mcaesar You have mentioned that you correct the java file to support the full "data:image/png;base64", but I'm afraid you forgot to upload the edited file. Thank u.
  • mukama
    mukama over 11 years
    @Stallman, 1) The default folder is 'Environment.getExternalStorageDirectory() + "/Pictures";' which translates to "sdcard/Pictures". 2) The decoder seems to work just fine but in case of any issues reffer to the original code(sourceforge.net/projects/migbase64) or get an alternative decoder(codecodex.com/wiki/Encode/Decode_to/from_Base64). 3)Yes. The unzipped Base64.java goes to "src/Base64.java". I understand that its a long solution but if you follow along step-by-step, it will work out. All the best.
  • Alston
    Alston over 11 years
    @mcaesar Thank u for the response. The add-in can work for some base64 string, saving it to a png file in the local system. But when the string is canvas.toDataURL();, it just can't work. Is anything wrong with the plugin? Or it's my code's problem.
  • mukama
    mukama over 11 years
    @Stallman, unfortunately, android's browser has limited functionality and "canvas.toDataURL();" will NOT work. ( More about this here: code.google.com/p/android/issues/detail?id=7901 ). ( Solution: code.google.com/p/todataurl-png-js ).
  • Alston
    Alston over 11 years
    @mcaesar I'll try on plugin, thank u. However, my working environment is PhoneGap+ Android system, and I assign the returned string created by canvas.toDataURL("image/png"); to image.src and it can display the canvas drawing.//the variable image is a DOM object:<img>. Do you have any idea?
  • Alston
    Alston over 11 years
    @mcaesar Maybe you can track my issue: stackoverflow.com/questions/12873382/…
  • Alston
    Alston over 11 years
    @mcaesar May I ask you that do you have the version: "Convert the canvas to ""JPG"" file format?" Thank u.
  • mukama
    mukama over 11 years
    @Stallman Its the same story as with PNG. Just make sure you dont forget the extension. :)
  • Alston
    Alston over 11 years
    @mcaesar Do you mean that if I write filename:XXX.jpg, the generated pic will be a JPG format file? I thought the plugin change the base64 string to .PNG file only.
  • ElHacker
    ElHacker over 11 years
    thank you. You saved me hours of figuring out how this would work!!. I did some little modifications to support mp3 files instead of PNG. Thanks again!!!!
  • MarkSmits
    MarkSmits about 11 years
    @mcaesar Thanks alot, got everything ready except for one line: byte[] decodedBytes = Base64.decode(b64String.getBytes()); gives me the following error: The method decode(byte[], int) in the type Base64 is not applicable for the arguments (byte[]) Since I am no pro in Java could you help me out? Would appreciate it.
  • mukama
    mukama about 11 years
    Hi @Mark. Please make sure that your base64 string is well formatted. See examples I provided. Thank you.
  • MarkSmits
    MarkSmits about 11 years
    @mcaesar Got it working, the line prevented me from compiling my code. I've changed it to: byte[] decodedBytes = Base64.decode( b64String.getBytes(), Base64.DEFAULT ); cause it required the extra parameter. I also had to remove the data:image/png;base64, part in Javascript cause the images were corrupt with it or substract 22 instead of 21 characters in the plugin. Maybe replacing the specific part of the string with nothing is more solid option, the con: you have to check per specific file type, jpg or png. Thanks for the response.
  • REJH
    REJH almost 11 years
    @mcaesar I found a bug in your code: you check if base64 starts with 'data:png' but one line above that you set base64 = ''; :)
  • lukaszkups
    lukaszkups over 10 years
    I'm using newest PhoneGap (coding in eclipse) and got this error: Error in an XML file: aborting build. (btw. I had to create plugins.xml file - is that ok that I haven't that file before?)
  • Reign.85
    Reign.85 almost 10 years
    can you post a git repos that follow cordova spec?