4

I used a jquery plugin to crop images. The plugin will crop the image and give it to me as a base64 encoded string. In order to upload it to S3, I need to convert this into a file and pass the file into the upload function. How can I do this? I tried a lot of things including decoding the string using atob. None worked.

Here's the code of the cropping plugin ('croppie') which gives me the encoded string :

imageCropper.croppie('result', {
    type: 'canvas',
    size: 'viewport',
    format: 'jpeg'
}).then(function (resp) {
  updateAvatar(resp);
});

I pass it to a function called updateAvatar. Here's the updateAvatar function :

updateAvatar({Meteor, Slingshot}, avatar) {
  const uploader = new Slingshot.Upload('userAvatarUpload');

  uploader.send(avatar, function (error, downloadUrl) {
    if (error) {
      // Log service detailed response.
      console.error('Error uploading', uploader.xhr.response);
      console.log(error);
    }
    else {
      console.log('uploaded', downloadUrl);
    }
  });
}

The uploader.send function expects a file or a url. It won't accept my encoded string.

The plugin which I use to upload files to S3 : https://github.com/CulturalMe/meteor-slingshot

THpubs
  • 7,804
  • 16
  • 68
  • 143
  • Possible duplicate [http://stackoverflow.com/questions/21227078/convert-base64-to-image-in-javascript-jquery](http://stackoverflow.com/questions/21227078/convert-base64-to-image-in-javascript-jquery) – IronAces Jul 29 '16 at 12:19
  • 1
    @DanielShillcock That answer render the image in the dom. I want the image to be converted to a file and passed into a server function. – THpubs Jul 29 '16 at 12:20
  • @THpubs It does render the image in the DOM, but the principle may be the same where you create an `Image` object and use that to communicate with S3. – IronAces Jul 29 '16 at 12:23
  • 1
    @THpubs Can you share code, or documentation on what S3 is expecting to receive in this function – IronAces Jul 29 '16 at 12:23
  • @Ionut I just added the sample code :-) – THpubs Jul 29 '16 at 12:25
  • @DanielShillcock Thanks. I just added some sample code and also a link to the file upload plugin i'm using. – THpubs Jul 29 '16 at 12:25
  • 1
    @THpubs Does this help? https://github.com/CulturalMe/meteor-slingshot/issues/114 – IronAces Jul 29 '16 at 12:30
  • @DanielShillcock Thanks but unfortunately, the cropping tool give me the cropped image in an encoded string. If I try to get the blob from the canvas I might get the un cropped image. I can however render the image to the dom as you have shown earlier and get the blob from there. I will try it as a last resort if I didn't find a better way. Thanks! :-) – THpubs Jul 29 '16 at 12:37

3 Answers3

11

It seems like the missing 'brick' in your code is a function that would take a base64-encoded image and convert it to a Blob.

So, I'm going to focus on that part exclusively with a short comment for each step.

The following function expects a string such as:

data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICA...

function base64ImageToBlob(str) {
  // extract content type and base64 payload from original string
  var pos = str.indexOf(';base64,');
  var type = str.substring(5, pos);
  var b64 = str.substr(pos + 8);

  // decode base64
  var imageContent = atob(b64);

  // create an ArrayBuffer and a view (as unsigned 8-bit)
  var buffer = new ArrayBuffer(imageContent.length);
  var view = new Uint8Array(buffer);

  // fill the view, using the decoded base64
  for(var n = 0; n < imageContent.length; n++) {
    view[n] = imageContent.charCodeAt(n);
  }

  // convert ArrayBuffer to Blob
  var blob = new Blob([buffer], { type: type });

  return blob;
}
Arnauld
  • 5,847
  • 2
  • 15
  • 32
  • Thanks. Your explanations are very helpful. – THpubs Jul 29 '16 at 16:04
  • Glad it helped. (And just to be clear about it, I won't mind at all if you accept @DanielShillcock's answer as promised. ^^) – Arnauld Jul 29 '16 at 16:14
  • Actually im stuck in there. His answers and comments helped me and your answer made things even more clear. Hope I can select both :P But I might wait for others to vote and then pick a good one. Hope everyone agree to it. – THpubs Jul 29 '16 at 16:17
3

Convert the base64 string to blob, to be used in upload to S3. There are tidier ways of doing this of course! :)

Original SO Answer here: https://stackoverflow.com/a/16245768/1350913

  imageCropper.croppie('result', {
  type: 'canvas',
  size: 'viewport',
  format: 'jpeg'
}).then(function(resp) {
  var contentType = 'image/png';
  var s3Blob = b64toBlob(resp, contentType);
  updateAvatar(s3Blob);
});

updateAvatar({
  Meteor,
  Slingshot
}, avatar) {
  const uploader = new Slingshot.Upload('userAvatarUpload');

  uploader.send(avatar, function(error, downloadUrl) {
    if (error) {
      // Log service detailed response.
      console.error('Error uploading', uploader.xhr.response);
      console.log(error);
    } else {
      console.log('uploaded', downloadUrl);
    }
  });
}

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var b64DataString = b64Data.substr(b64Data.indexOf(',') + 1);      
  var byteCharacters = atob(b64DataString);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

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

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, {
    type: contentType
  });
  return blob;
}
Community
  • 1
  • 1
IronAces
  • 1,857
  • 1
  • 27
  • 36
  • Great thanks a lot! It worked. But a small correction. The line `var byteCharacters = atob(b64Data);` gives an error because the encoded string have the file type and other data. We need to remove them like this : `var b64DataString = b64Data.substr(b64Data.indexOf(',') + 1);` Ref : http://stackoverflow.com/questions/24289182/how-to-strip-type-from-javascript-filereader-base64-string Can you please correct is so I can accept the answer? – THpubs Jul 29 '16 at 12:56
  • Thanks, was having trouble with uploading corrupted file to an S3 presigned URL but this resolved it – Amartya Mishra Mar 27 '23 at 07:09
1

The base64ToFile function (.ts) converts the base64 string into a File. The codeUnits and charCodes part make sure you can read Unicode text as ASCII by converting the string such that each 16-bit unit occupies only one byte.
Finally the download function (.ts) downloads the converted file from your browser to your local machine.

function base64ToFile(base64data: string, myFileNameWithdotExtention: string,
  fileType: string): File {
  let content = decodeURIComponent(escape(window.atob(base64data)));
  let fileName = myFileNameWithdotExtention;
  const codeUnits = Uint16Array.from(
    { length: content.length },
    ( _, index) => content.charCodeAt(index)
  );
  const charCodes = new Uint8Array(codeUnits.buffer);
  const type = fileType; // 'text/csv' for instance
  const blob = new Blob([charCodes], { type });
  return new File([blob], fileName, { lastModified: new Date().getTime(), type });

}

download(){
    let res: string = getMyDataFromSomewhere(); // base64 string
    let data = base64ToFile(res);
    let element = document.createElement('a');
    window.URL = window.URL || window.webkitURL;
    element.setAttribute('href', window.URL.createObjectURL(data));
    element.setAttribute('download', data.name);
    element.style.display = 'none';
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
}
Pepe Alvarez
  • 1,218
  • 1
  • 9
  • 15