28

My client is offering the user to pick a picture, crop and resize it and then display it (in a <img> DOM element).
If the picture is fine, the user can upload it to the server so it can be saved.

I would like to do the upload thanks to an Ajax request.

I found tons of examples on the internet to upload the original image retrieved from the client PC. For instance:

$( '#my-form' )
  .submit( function( e ) {
    $.ajax( {
      url: 'http://host.com/action/',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );

This works properly if I decide to upload the picture retrieved through the form input.

In my case I want to upload the modified picture (saved in a <img> element) instead of the original one.
This picture is stored as a base64 picture (For information: I used the croppie.js library to generate the image).

I don't know how to upload this picture with Ajax.

I tried to upload it as a regular parameter but on the server side the img is an empty string:

var url = 'http://host.com/action/';
var data = {};
data.img = $('img#generated-image').attr('src');

$.ajax({url: url, type: "POST", data: data})
  .done(function(e){
    // Do something
  });
// RESULTS in a empty data.img on the server side.

My problem being the server having an empty string when retrieving the "img" parameter. I suspect the image is maybe too big to be passed to the server or some other issues that I don't understand... .

So I'm wondering what is the proper way to send a base64 image to the server using an Ajax request WITHOUT using a form.

Thanks for your help.

EDIT

Seems to be an xmlHTTP POST parameter size issue. I tried to reduce the number of characters of the string representation of the image and the server is now able to retrieve it.

EDIT2

post_max_size is set to 8M in the php.ini file wheras the picture size is only 24K. So the problem is not there.
I'm using PHP with the Symfony2 framework.
Maybe a limitation from Symfony2.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Nitseg
  • 1,267
  • 1
  • 12
  • 21
  • What sort of server are you using to parse these requests? The limit may be there, not on the client-side. – Jeremy Jan 14 '16 at 01:46
  • 1
    @JeremyBanks: I'm using PHP with the Symfony2 framework. I checked the post_max_size in my php.ini and it is set to 8M. The picture is only 24K big. So maybe Symfony is limitating the max size. I didn't find anything related to this so far... . – Nitseg Jan 14 '16 at 01:57

2 Answers2

55

I finally decided to convert the base64 image to a Blob so it can be sent via an Ajax request with the formData object as follows. It saves upload bandwidth (base64 takes 33% more bits than its binary equivalent) and I couldn't find the reason for no transmission of base64 parameter (due to size limitation somewhere for sure).

The base64ToBlob function is based on this answer to another question.

function base64ToBlob(base64, mime) 
{
    mime = mime || '';
    var sliceSize = 1024;
    var byteChars = window.atob(base64);
    var byteArrays = [];

    for (var offset = 0, len = byteChars.length; offset < len; offset += sliceSize) {
        var slice = byteChars.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);
    }

    return new Blob(byteArrays, {type: mime});
}

My JS code:

var url = "url/action";                
var image = $('#image-id').attr('src');
var base64ImageContent = image.replace(/^data:image\/(png|jpg);base64,/, "");
var blob = base64ToBlob(base64ImageContent, 'image/png');                
var formData = new FormData();
formData.append('picture', blob);

$.ajax({
    url: url, 
    type: "POST", 
    cache: false,
    contentType: false,
    processData: false,
    data: formData})
        .done(function(e){
            alert('done!');
        });

In Symfony2 I can retrieve the image thanks to:

$picture = $request->files->get('picture');
Community
  • 1
  • 1
Nitseg
  • 1,267
  • 1
  • 12
  • 21
  • 3
    Amusingly, that code you found appears to be mostly from [my answer here](http://stackoverflow.com/a/16245768/1114), with some variables renamed and wrapped for use as a Node module. I love that he put his own name on the copyright, when by line count it's more my code than his. Maybe I should sue him. ;) I'm going to take the liberty of changing the link in your post to point to my answer instead. – Jeremy Jan 14 '16 at 21:00
  • 4
    Yeah, I saw a couple of base64ToBlob function versions that were very similar. Also some other functions doing almost the same thing. Happy to find the root author. :-) Thanks for your help then. – Nitseg Jan 14 '16 at 22:51
  • 45
    isnt all code posted on this site like creative commons or something? if you are upset about someone copying your code id presume not to ever post it publicly. my two cents. – Coty Embry Jul 16 '17 at 22:23
  • 1
    @CotyEmbry I think that Jeremy was upset that who copied his code put the copyright on it, while the code wasn't written by him, not by the re-usage of his code :) – Andrea Gobetti Mar 03 '21 at 16:42
3

Nitseg's answer works nicely. Also, I wanted to add the following lines if you must use auth token in your ajax call. Again, take a look at Nitseg's answer for more details first.

var formData = new FormData();
var token = "<YOUR-TOKEN-HERE>";
formData.append("uploadfile", mediaBlob);        

jQuery.ajax({
    url: url,
    type: "POST",
    cache: false,
    contentType: false,
    processData: false,
    data: formData,
    beforeSend: function (xhr){ 
        xhr.setRequestHeader("Authorization", "Bearer " + token); 
    }
})
.done((e) => {
    // It is done.
})
.fail((e) => {
    // Report that there is a problem!
}); 
omt66
  • 4,765
  • 1
  • 23
  • 21