Base64 Upload Image Issue - javascript

Sorry for the long question, but I wanted to express it as well as I could, and ask it in a way that is understood easily. I have a program that allows a user to crop an image using croppie.js in JavaScript, and send the image to a Hunchentoot server on the backend running a Lisp program. I am having an issue saving the Base64 image to a .png file once the user uploads it. Once the post request is sent to the server I am getting the Base64 image as a string, removing invalid characters from the Base64 request by creating a subsequence of the string without the heading sent by the post request and also substituting the "%" character for the "+" character to make the Base64 valid. Next I remove the substring +3D+3D at the end of my string, because the s-base64 library that I am using in Common Lisp complains +3D+3D is invalid padding, and I replace it with "==" which is considered valid padding. Next I create a byte array by using the s-base64 library to translate the Base64 string to a byte array, and store it in a variable. Then I loop through the byte array and write each byte to the output file. When that is finished I decided to print the end of the byte array to the console so that I could see if the output and ending padding is valid, which it appears to be. Here is that part of the code, with comments to make it clearer:
(define-easy-handler (handle-image :uri "/handle-image.html") ()
(let ((data-source (hunchentoot:raw-post-data :force-text t))) ;get Base64 string
(let ((new-string (subseq data-source 36))) ;create a subsequence of Base64 string
(let ((final-string (substitute #\+ #\% new-string))) ;substitute % for +
(let ((end (search "+3D+3D" final-string))) ;find the invalid padding
(setf final-string (concatenate 'string (subseq final-string 0 end) "==")) ;add valid padding
(let ((byte-array (with-input-from-string (in final-string) ;create byte array (or simple-vector) out of Base64 string
(decode-base64-bytes in))))
(with-open-file (out "path/path/path/path/profile-image.png" ;create output stream to store file
:direction :output
:if-exists :supersede
:element-type 'unsigned-byte)
(dotimes (i (length byte-array)) ;write each byte from the byte array to output stream
(write-byte (aref byte-array i) out)))) ;close stream
(format t "!!!!!!!!: ~a" (subseq final-string (- (length final-string) 30))))))) ;print ending to console to ensure proper padding
"Upload Successful") ;send response to client
And here is some of my JavaScript code:
$(document).ready(function(){
$image_crop = $('#image_demo').croppie({
enableExif: true,
viewport: {
width:200,
height:200,
type:'square' //circle
},
boundary:{
width:300,
height:300
}
});
As you can see, I first create the cropper. I allow the user to have a 200 x 200 square to crop, and the total size of the cropping space is 300 x 300. There are no issues with that part of the code:
$('#upload_image').on('change', function(){
var reader = new FileReader();
reader.onload = function (event) {
$image_crop.croppie('bind', {
url: event.target.result
}).then(function(){
console.log('jQuery bind complete');
});
}
Above I bind the image they've uploaded to the cropper (like if you upload a Facebook image), when they select a file. Again, no issues:
reader.readAsDataURL(this.files[0]);
$('#uploadImageModal').modal('show');
Above I read the file that they've selected and then the modal "pops up" as if you're cropping a Facebook or Instagram photo:
$('.crop_image').click(function(event){
$image_crop.croppie('result', {
type: 'canvas',
size: 'viewport'
}).then(function(response){
$.ajax({
url:"handle-image.html",
type: "POST",
data:{"image": response},
success:function(data){
$('#uploadImageModal').modal('hide');
$('#uploaded_image').html(data);
}
});
})
});
Above I upload the ajax request, and if the upload was successful they will get a message from the server that it was success, and I hide the modal for image cropping as well.
Now the issue is that the image is simply blank. I know that the Base64 is valid because I used a Base64 conversion tool to see if the string was valid. I also went back and did my research in regards to Bits, Bytes, Pixels, and dimensions of images to see how the computer interacts with them, so i'm not sure why my image is simply displaying blank. Here is a look at what the cropper looks like on my website:
The bind is working, and then I get the message that the upload was successful. BUT after writing the image and viewing it in the file system it is either a blank image, or will sometimes say that the image type is not supported.
The reason why I am tagging PHP in this post is because I am sure some people have had similar issues in PHP with uploading a cropped image via ajax, and some of those solutions might be applicable in this case, but obviously will require me translating the solution to the lisp syntax. My assumption is that something is wrong with my code when I translate the string to a byte array and write it to a file, but I thought it'd be good to post other sections of my code if I am overlooking something.

As Brad commented, you should first try to use binary uploads directly.
That aside: if you encounter a % in a base64-encoded string, it most likely means that the entire thing is additionally URL-encoded. A quick apropos search gave do-urlencode as a library to decode that. Replacing % with + makes valid base64, but the result does not necessarily represent valid jpg.
Also: use let* instead of nested let forms. Maybe use write-sequence instead of byte-wise output.

Thanks to the answer from #Brad and #Svante I was able to solve the problem. I decided to put the image that I want to upload within a form element, add the blob image from the canvas as FormData for the form, and then send the FormData via an ajax post request:
$('.crop_image').on('click mousedown touchstart', function(event){ //When the crop image button is pressed event.
$image_crop.croppie('result', { //Get the result of the cropper
size: 'viewport', //Set the size to the viewport size (180 x 120)
format: 'png', //.png format
type: 'blob'
}).then(function (blob){
$form = $('#uploadForm');
var fd = new FormData($form);
fd.append('upload_image', blob);
$.ajax({
url: "handle-image.html",
type: "POST",
data: fd,
processData: false,
contentType: false,
success: function (data){
$('#uploadImageModal').modal('hide');
$('#uploaded_image').html(data);
}
});
})
});
Here I just decided to change the croppie result from the type "canvas"to the type "form", and specify that it will be a .png file. From there I added the form data from my newly created form to the .ajax request and went from there. Thanks to #Brad and #Svante for the help here.

Related

Javascript: Temporarily store and display image received from backend as binary data?

I am currently trying to display an image, which I receive from a backend server in a particular way/format, on the screen of the browser.
My problem is acutally closely related to this issue, for which no real answer exists.
Here is a screenshot displaying what the backend server's response looks like:
payload.data contains the data of the image, which is a green cloud (also attached at the end of this post for reference).
My first, probably very stupid, question would be: What kind of format/encoding is that?
Anyway, here is what I then further tried to process the data:
const blob = new Blob([action.payload.data], { //contains the data
type: action.payload.headers["content-type"] // 'image/png'
})
console.log("blob: ", blob);
const url = URL.createObjectURL(blob);
console.log("url : ", url)
As a result, the blob is sucessfully created, as well as the url. However, when I open that link, no image gets displayed.
I am stuck here and would appreaciate any kind of helpful hint pointing out where I am doing a mistake here.
Thanks very much for your support in advance.
PS: As promised, here is the actual png image:
It seems like your data attribute is still in binary format. You need to convert the hex into base64 in order to display the image.
First, if the server you're fetching the image form is yours, I would recommend encoding the image on the server before sending it to the client.
If the server is not yours and you can't change the data that is being returned, try something like this:
function hexToBase64(str) {
return btoa(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
And then use it like this:
const img = document.createElement('img');
img.src = 'data:image/jpeg;base64,' + hexToBase64('your-binary-data');
document.body.appendChild(img);
reference: How to display binary data as image - extjs 4

PouchDB returning wrong image attachment type

I'm trying to get PouchDB to return a small image attachment. When I upload the image through Fauxton, my app successfully returns a Blob with type: 'image/svg+xml' that renders correctly in my <img> element (using URL.createObjectURL).
However, if I have the user upload an image, and then put the file Blob to my remote Pouch database, I have problems. In this case, PouchDB.getAttachment() now returns a Blob with type: 'application/octet-stream' instead of 'image/svg+xml', despite having uploaded it with the correct content_type. This image does not render, giving me a generic browser img placeholder.
Why could this be occurring?
Step 1 Here is how I retrieve the image from the user:
var image = new Blob([fs.readFileSync(fileLocation, 'utf-8')], {
type: 'image/svg+xml'
})
Step 2 Here is how I put my document (and attachment) to PouchDB:
var doc = await PouchDB.get(docID, {attachments: true})
doc._attachments = {
'logo.svg': {
content_type: 'image/svg+xml',
data: image
}
}
await PouchDB.put(doc)
You got 2 problems with reading your file. First is that you can't treat binary data as UTF8 (may not be a real problem with SVG file, but with other images surely is), and the second one is that you have to base64 encode it.
There are many different approaches to base64 encode a blob, but the easiest solution would be to change the method of putting it from put to putAttachment. Something like this:
HTML:
...
<input type="file" id="inputFile">
...
JS:
...
var inputFile = document.querySelector('#inputFile');
var file = inputFile.files[0];
db.putAttachment('mydoc', 'myfile', file, file.type).then(...)
...

AJAX - Weird characters in img tag after request response

I'm have trouble solving an issue with an AJAX request I'm making (I'm new to AJAX by the way). I have an API set-up and I'd like to retrieve a png image using an Authorization header which uses a token that I supply (which is stored in local storage). So for example, if i were to access the image with the Auth header then I would do this...
$.ajaxSetup({headers: {"Authorization" : localStorage.token}});
I'm able to retrieve the image. I can see it in the "Network" tab in Chrome, but when I append it to my div using this below...
$.ajax({
//Use commas for several parameters
type: 'GET',
url: *url of image*,
contentType: 'image/png',
success: function (data) {
binary = data;
$("#image").attr("src", 'data:image/png;base64,'+ data);
}
...it comes out in this weird character format (as seen below):
div id="image" src="data:image/png;base64, PNG
IHDRww^ÀþIDATxÚìÝ|ÔWº?þ½¿ÿ½Ý.ÅâÉd2îdâ®BB ÁÝÝ
)îÞbÅÝ¥TÐzi)Ô ÞRÙn»rï]»+w·{þçùÌ<Ãd]ùýV¾çõz¿H&I°Ï÷ç<çï}OÊø;æO1ªzhÔÀdÆþKȤ!......."
etc
I'd like it to come back as an image or a b64 string which I can simply put into the src param. I've searched online everywhere and cannot seem to find an answer.
Any ideas? I appreciate your time greatly.
This is the response I receive
Your code is downloading the image, which is in binary format.
You need to encode the binary to base64 in order to display the image in base64. Use btoa as explained in this answer: https://stackoverflow.com/a/247261/2223027
It seems like you are getting image file in binary data format and not the base64 format as you are expecting to put in the img tag src attribute.
If you do have image url you can put it in the img src directly no need of ajax call.

how to post Base64 encoded image to server using $.ajax?

I am making a phonegap app, in which the user submits a photo from his camera, i get it in a Base64 encoding form.
var posData=extImage1;
$.ajax({
type: 'POST',
data: posData,
timeout:50000,
url: 'http://192.168.137.1/bee/services/add_photo.php',
success: function(data){
alert(data);
addToList();
},
error: function(){
alert('Please check your internet connection');
}
});
server side the code is saved to the database, but when selected and used as div background; it won't work no matter what!
BG='url(' + "data:image/jpeg;base64," + item.car_intImage3+ ')';
$('#item').css('background',BG);
why this is happening ? my guess is that during posting the data it got scrambled somehow.
I tried to save the image on server using the code from this question, but it just gives a corrupted image.
Update:
Using this decoder i submitted the posted 64 code and it also gives corrupted image. i think that means that there is something wrong with my post method. any suggestions?
It would be a lot cleaner and easier if you use Dropzone.js
Take a look at Dropzone.js Implementation. It is quite easy
This is how you can instantiate it
var myDropzone = new Dropzone("div#myId", { url: "/file/post"});
You can also listen to a lot of events like Adding a file, Removing a file
For Ex:
Dropzone.options.myAwesomeDropzone = {
init: function() {
this.on("addedfile", function(file) { alert("Added file."); });
}
};
EDIT: The Above solutions will work if you are storing the image in your disk and just the image location in your database. In fact it would be a lot better if you do this way.
Now coming to your method of working with images, You would want to go through this link how-can-i-store-and-retrieve-images-from-a-mysql-database-using-php which has a straight forward answer
Also, Check the datatype that you are using, may be its truncating he encoded data that you are storing in the database and hence you might be getting corrupted images. Also check with the above method of storing the images in the database mentioned in the link.
Hope this solves your problem

Image not showing using chrome filesystem toURL

I have the following code to write an image into the filesystem, and read it back for display. Prior to trying out the filesystem API, I loaded the whole base64 image into the src attribute and the image displayed fine. Problem is the images can be large so if you add a few 5MB images, you run out of memory. So I thought I'd just write them to the tmp storage and only pass the URL into the src attribute.
Trouble is, nothing gets displayed.
Initially I thought it might be something wrong with the URL, but then I went into the filesystem directory, found the image it was referring to and physically replaced it with the real binary image and renamed it to the same as the replaced image. This worked fine and the image is displayed correctly, so the URL looks good.
The only conclusion I can come to is that the writing of the image is somehow wrong - particularly the point where the blob is created. I've looked through the blob API and can't see anything that I may have missed, however I'm obviously doing something wrong because it seems to be working for everyone else.
As an aside, I also tried to store the image in IndexedDB and use the createObjectURL to display the image - again, although the URL looks correct, nothing is displayed on the screen. Hence the attempt at the filesystem API. The blob creation is identical in both cases, with the same data.
The source data is a base64 encoded string as I mentioned. Yes, I did also try to store the raw base64 data in the blob (with and without the prefix) and that didn't work either.
Other info - chrome version 28, on linux Ubuntu
//strip the base64 `enter code here`stuff ...
var regex = /^data.+;base64,/;
if (regex.test(imgobj)) { //its base64
imgobj = imgobj.replace(regex,"");
//imgobj = B64.decode(imgobj);
imgobj = window.atob(imgobj);
} else {
console.log("it's already :", typeof imgobj);
}
// store the object into the tmp space
window.requestFileSystem(window.TEMPORARY, 10*1024*1024, function(fs) {
// check if the file already exists
fs.root.getFile(imagename, {create: false}, function(fileEntry) {
console.log("File exists: ", fileEntry);
callback(fileEntry.toURL(), fileEntry.name);
//
}, function (e) { //file doesn't exist
fs.root.getFile(imagename, {create: true}, function (fe) {
console.log("file is: ", fe);
fe.createWriter(function(fw){
fw.onwriteend = function(e) {
console.log("write complete: ", e);
console.log("size of file: ", e.total)
callback(fe.toURL(), fe.name);
};
fw.onerror = function(e) {
console.log("Write failed: ", e.toString());
};
var data = new Blob([imgobj], {type: "image/png"});
fw.write(data);
}, fsErrorHandler);
}, fsErrorHandler);
});
// now create a file
}, fsErrorHandler);
Output from the callback is:
<img class="imgx" src="filesystem:file:///temporary/closed-padlock.png" width="270px" height="270px" id="img1" data-imgname="closed-padlock.png">
I'm at a bit of a standstill unless someone can provide some guidance...
UPDATE
I ran a test to encode and decode the base64 image with both the B64encoder/decoder and atob/btoa -
console.log(imgobj); // this is the original base64 file from the canvas.toDataURL function
/* B64 is broken*/
B64imgobjdecode = B64.decode(imgobj);
B64imgobjencode = B64.encode(B64imgobjdecode);
console.log(B64imgobjencode);
/* atob and btoa decodes and encodes correctly*/
atobimgobj = window.atob(imgobj);
btoaimgobj = window.btoa(atobimgobj);
console.log(btoaimgobj);
The results show that the btoa/atob functions work correctly but the B64 does not - probably because the original encoding didn't use the B64.encode function...
The resulting file in filesystem TEMPORARY, I ran through an online base64 encoder for comparison and the results are totally different. So the question is - while in the filesystem temp storage, is the image supposed to be an exact image, or is it padded with 'something' which only the filesystem API understands? Remember I put the original PNG in the file system directory and the image displayed correctly, which tends to indicate that the meta-data about the image (eg. the filename) is held elsewhere...
Can someone who has a working implementation of this confirm if the images are stored as images in the filesystem, or are padded with additional meta-data?
So to answer my own question - the core problem was in the base64 encoding/decoding - I've since then changed this to use things like ajax and responseTypes like arraybuffer and blob and things have started working.
To answer the last part of the question, this is what I've found - in the filesystem tmp storage, yes the file is supposed to be an exact binary copy - verified this in chrome and phonegap.

Categories