13
votes

Using firebase 3.0.x, is it possible to save a base64 encoded image to the new Firebase Storage service?

I am using canvas to resize images in the browser prior to uploading them, and output them as a base64 jpeg. I know that the Storage api can accept Blobs, but IE9 support is needed for my current project.

6
A solution that worked for me is provided in stackoverflow.com/questions/52157253/…Yossi

6 Answers

9
votes

The latest version of the Firebase SDK supports base64 image uploads. Simply use the putString method from Firebase Storage.

https://firebase.google.com/docs/reference/js/firebase.storage

One small caveat is that sometimes you'll have a base64 String with unnecessary whitespace. For example, I've found that the cordova Camera plugin returns base64 with unnecessary whitespace. The Storage SDK will fail to upload this because JavaScript can't perform it's native atob function - something the Firebase JS does under the hood. You'll have to strip the whitespace - see DOM Exception 5 INVALID CHARACTER error on valid base64 image string in javascript

19
votes

You only need to use the putString function without converting the BASE64 to blob.

firebase.storage().ref('/your/path/here').child('file_name')
.putString(your_base64_image, ‘base64’, {contentType:’image/jpg’});

Make sure to pass the metadata {contentType:’image/jpg’} as the third parameter (optional) to the function putString in order for you to retrieve the data in an image format.

or simply put:

uploadTask = firebase.storage().ref('/your/path/here').child('file_name').putString(image, 'base64', {contentType:'image/jpg'});
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, // or 'state_changed'
  function(snapshot) {
    // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
    var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
    console.log('Upload is ' + progress + '% done');
    switch (snapshot.state) {
      case firebase.storage.TaskState.PAUSED: // or 'paused'
        console.log('Upload is paused');
        break;
      case firebase.storage.TaskState.RUNNING: // or 'running'
        console.log('Upload is running');
        break;
    }
  }, function(error) {
    console.log(error);
}, function() {
  // Upload completed successfully, now we can get the download URL
  var downloadURL = uploadTask.snapshot.downloadURL;
});

You can then use the downloadURL to save to firebase.database() and/or to put as an src to an <img> tag.

5
votes

Yes, it's possible now. You should use Firebase Storage new method called putString. You may read spec here.

So, Firebase spec says that you have now two methods to store Base64 string and Base64url string:

// Base64 formatted string
var message = '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
ref.putString(message, 'base64').then(function(snapshot) {
  console.log('Uploaded a base64 string!');
});

// Base64url formatted string
var message = '5b6p5Y-344GX44G-44GX44Gf77yB44GK44KB44Gn44Go44GG77yB';
ref.putString(message, 'base64url').then(function(snapshot) {
  console.log('Uploaded a base64url string!');
})

From my experience, using putString(message, 'base64url') constantly returns Error about bad formated Base64 string code: "storage/invalid-format", message: "Firebase Storage: String does not match format 'base64': Invalid character found". The solution is to cut off beginning of string data:image/jpeg;base64, and use first method instead putString(message, 'base64'). Then it works.

2
votes

If you use canvas.toBlob() you'll get the byte[] that you need to pass into Firebase Storage.

Quick example:

function save() {
    var canvas = document.getElementById("canvas");
    canvas.toBlob(blob => {
      var storage = firebase.app().storage().ref();
      var name = id + "/" + (new Date()).getTime() + ".png";
      var f = storage.child("drawings/" + name);
      var task = f.put(blob);
      task.on('state_changed', function(snapshot) {
      }, function(error) {
        console.error("Unable to save image.");
        console.error(error);
      }, function() {
        var url = task.snapshot.downloadURL;
        console.log("Saved to " + url);

        var db = firebase.database();
        var chats = db.ref().child("chats");
        chats.child(id).child("drawingURL").set(url);
      });
    });
  };

Otherwise you'll have to convert the base64 yourself, for example with atob().

1
votes

Here are two values I use to help with support on .toBlob() it is less performant however, it gets the job done.

This method takes in the base64 string, the content type (IE: image/png) and a callback for when the blob has been constructed using atob()

var b64_to_blob = function(b64_data, content_type, callback) {
        content_type = content_type || '';
        var slice_size = 512;

        var byte_characters = atob(b64_data);
        var byte_arrays = [];

        for(var offset = 0; offset < byte_characters.length; offset += slice_size) {
            var slice = byte_characters.slice(offset, offset + slice_size);

            var byte_numbers = new Array(slice.length);
            for(var i = 0; i < slice.length; i++) {
                byte_numbers[i] = slice.charCodeAt(i);
            }

            var byte_array = new Uint8Array(byte_numbers);

            byte_arrays.push(byte_array);
        }

        var blob = new Blob(byte_arrays, {type: content_type});
        callback(blob);
    };

I use this method to get the base64 value of a direct link when necessary, In my case it's to download a users Facebook photo when they register on my application.

    var image_link_to_b64 = function(url, content_type, callback) {
        var image = new Image();
        image.crossOrigin = 'Anonymous';
        image.onload = function() {
            var canvas = document.createElement('CANVAS');
            var context = canvas.getContext('2d');
            var data_url;
            canvas.height = this.height;
            canvas.width = this.width;
            context.drawImage(this, 0, 0);
            data_url = canvas.toDataURL(content_type);
            data_url = data_url.substr(22);
            callback(data_url);
            canvas = null;
        };
        image.src = url;
    };

Below is how it looks when adding the information to firebase storage

$fileService.image_link_to_b64(facebook_info.photoURL + '?width=512&height=512', 'image/png', function(b64) {
     $fileService.b64_to_blob(b64, 'image/png', function(blob) {
        $fileService.upload_user_photo(blob, 'image/png', function(url) {
            // Firebase storage download url here
        }
     }
  }

Incase you're wondering upload_user_photo simply uploads to firebase storage:

var upload_user_photo = function(data, blob, callback) {
        var upload_task = user_photo_reference.child('user_photo').put(data);
        upload_task.on('state_changed', function(snapshot) {

        }, function(error) {
            alert('error: ', error);
        }, function() {
            callback(upload_task.snapshot.downloadURL);
        });
    };]

For IE9 see this polyfill: https://github.com/eligrey/Blob.js/blob/master/Blob.js

0
votes

This solution works for me using the Google Cloud Storage API.
But it should work also with the Firebase one by replacing the file.save with the ref put method.

const file = storage.file(file_path_in_gs)
const contents = new Uint8Array(Buffer.from(base64ImgStr, 'base64'))
  file.save(contents,
    {
      contentType: img_type,
      metadata: {
        metadata: {
          contentType: img_type,
          firebaseStorageDownloadTokens: uuid()
        }
      }
    }
    , () => { })