Upload HTML5 canvas as a blob - javascript

I have a canvas, which I can paint to the DOM without a problem as well as save for the user locally using:
storCanvas.toBlob(function(blob) { saveAs(blob, name + ".png");});
(note: I've included canvas-toBlob.js for cross platform support, from http://eligrey.com/blog/post/saving-generated-files-on-the-client-side)
Now, what I would like to do is send the same canvas element to the server. I thought I could do something like:
storCanvas.toBlob(function(blob) {upload(blob);});
where upload(blob); is:
function upload(blobOrFile)
{
var xhr = new XMLHttpRequest();
xhr.open('POST', 'upload_file.php', true);
xhr.onload = function(e) { /*uploaded*/ };
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable)
{
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value;
}
};
xhr.send(blobOrFile);
}
(cribbed from: http://www.html5rocks.com/en/tutorials/file/xhr2/)
Now, I thought I this would work, but my PHP page (which I can do a POST to successfully using a standard HTML form) reports back (via firebug) that it got an invalid file of 0kB. I assume that the issue is in my conversion to a blob, but I'm not sure the correct way to go about it...

I came across the same problem when learning about blobs myself. I believe the problem is in submitting the blob itself through the XMLHttpRequest rather than putting it inside a FormData like this:
canvas.toBlob(function(blob) {
var formData = new FormData(); //this will submit as a "multipart/form-data" request
formData.append("image_name", blob); //"image_name" is what the server will call the blob
upload(formData); //upload the "formData", not the "blob"
});
Hope this helps.

You can use this module for upload blob.
blobtools.js (https://github.com/extremeFE/blobtools.js)
//include blobtools.js
blobtools.uploadBlob({ // upload blob
blob: blob,
url: uploadUrl, // upload url
fileName : 'paste_image.png' // upload file name
callback: callback // callback after upload
});

Not sure if want this. But how about converting the canvas element into image data url and then sending it to the server and then writing it on the server.
Something like
function canvas2DataUrl(canvasElementId){
var canvasElement = document.getElementById("canvasElementId");
var imgData = canvasElement.toDataURL("image/png");
return imgData;
}

Related

Losing quality in image when sending to back end

The user uploads a photo which is then edited and cropped using Croppie. The edited and cropped version is the one that needs to be saved in the back end.
The current code takes the edited image, saves it in the database though when fetched, it displays the edited image though it has lost colour/not showing colours (displayed below)
Image being sent:
Image fetched:
And here is the code:
<input id="uploadAvatar" type="file" name="file" accept="image/*" />
$('#uploadAvatar).on('change', function(){
var reader = new FileReader();
reader.onload = function (e) {
var blob = dataURItoBlob(e.target.result;);
var fd = new FormData();
fd.append("file", blob);
//SERVICE HANDLES THE HTTP REQUEST
httpUploadAvatarServ('../p3sweb/FileUploadServlet', fd, function (callback) {
console.log(callback)
});
}
}
function dataURItoBlob(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: 'image/jpeg '});
}
Question
Why is it that the image is losing quality/colour?
I think your issue may be in the dataURItoBlob function. Here's how I go about getting the base64 data with croppie before sending it to the server. Tried to abridge my code for simplicity... Hopefully I didn't make it worse:
function PlaceLargeImg (input, imgId, zindex) {
var $imgel = $(imgId);
var reader = new FileReader();
reader.onload = function(e) {
// Sets preview image src to the value of file input.
$imgel.attr('src', e.target.result);
// Bind croppie
cropperlg.bind({
url: $imgel.attr('src')
});
// On click method which crops and sets results to html input.
$('.btn-crop-lg').on('click', () => {
cropperlg.result({
type: 'canvas',
size: {width: 1000, height: 400}
}).then(function(result) {
$('#base64lg').val(result); // Resulting cropped img base64 that gets sent to server.
$('#uploadLg').submit();
});
});
};
reader.readAsDataURL(input.files[0]);
}
EDIT: If your image looks good in a clint-side preview AFTER parsing through dataUritoBlob, then the issue is on the server. In this case, you'll need to post your server side code.

Cropping a profile picture in Node JS

I want the user on my site to be able to crop an image that they will use as a profile picture. I then want to store this image in an uploads folder on my server.
I've done this using php and the JCrop plugin, but I've recently started to change the framework of my site to use Node JS.
This is how I allowed the user to crop an image before using JCrop:
$("#previewSub").Jcrop({
onChange: showPreview,
onSelect: showPreview,
aspectRatio: 1,
setSelect: [0,imgwidth+180,0,0],
minSize: [90,90],
addClass: 'jcrop-light'
}, function () {
JcropAPI = this;
});
and I would use php to store it in a folder:
<?php
$targ_w = $targ_h = 300;
$jpeg_quality = 90;
$img_r = imagecreatefromjpeg($_FILES['afile']['tmp_name']);
$dst_r = ImageCreateTrueColor( $targ_w, $targ_h );
imagecopyresampled($dst_r,$img_r,0,0,$_POST['x'],$_POST['y'],
$targ_w,$targ_h,$_POST['w'],$_POST['h']);
header("Content-type: image/jpeg");
imagejpeg($dst_r,'uploads/sample3.jpg', $jpeg_quality);
?>
Is there an equivalent plugin to JCrop, as shown above, using Node JS? There are probably multiple ones, if there are, what ones would you recommend? Any simple examples are appreciated too.
EDIT
Because the question is not getting any answers, perhaps it is possible to to keep the JCrop code above, and maybe change my php code to Node JS. If this is possible, could someone show me how to translate my php code, what would would be the equivalent to php above?
I am very new to Node, so I'm having a difficult time finding the equivalent functions and what not.
You could send the raw image (original user input file) and the cropping paramenters result of JCrop.
Send the image enconded in base64 (string), when received by the server store it as a Buffer :
var img = new Buffer(img_string, 'base64');
ImageMagick Doc : http://www.imagemagick.org/Usage/files/#inline
(inline : working with base64)
Then the server has the image in a buffer and the cropping parameters.
From there you could use something like :
https://github.com/aheckmann/gm ...or...
https://github.com/rsms/node-imagemagick
to cast the modifications to the buffer image and then store the result in the file system.
You have other options, like manipulating it client-side and sending the encoded image result of the cropping.
EDIT : First try to read & encode the image when the user uses the input :
$('body').on("change", "input#selectImage", function(){readImage(this);});
function readImage(input) {
if ( input.files && input.files[0] ) {
var FR = new FileReader();
FR.onload = function(e) {
console.log(e.target.result);
// Display in <img> using the b64 string as src
$('#uploadPreview').attr( "src", e.target.result );
// Send the encoded image to the server
socket.emit('upload_pic', e.target.result);
};
FR.readAsDataURL( input.files[0] );
}
}
Then when received at the server use the Buffer as mentioned above
var matches = img.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/), response = {};
if (matches.length !== 3) {/*invalid string!*/}
else{
var filename = 'filename';
var file_ext = '.png';
response.type = matches[1];
response.data = new Buffer(matches[2], 'base64');
var saveFileAs = 'storage-directory/'+ filename + file_ext;
fs.unlink(saveFileAs, function() {
fs.writeFile(saveFileAs, response.data, function(err) {if(err) { /* error saving image */}});
});
}
I would personally send the encoded image once it has been edited client-side.
The server simply validates and saves the file, let the client do the extra work.
As promised, here is how I used darkroom.js with one of my Express projects.
//Location to store the image
var multerUploads = multer({ dest: './uploads/' });
I upload the image first, and then allow the user to crop it. This is because, I would like to keep the original image hence, the upload jade below:
form(method='post', action='/user/image/submit', enctype='multipart/form-data')
input(type='hidden', name='refNumber', value='#{refNumber}')
input(type='file', name='photograph' accept='image/jpeg,image/png')
br
input(type='submit', value='Upload image', data-role='button')
Here is the form I use to crop the image
//- figure needed for darkroom.js
figure(class='image-container', id='imageContainer')
//specify source from where it should load the uploaded image
img(class='targetImg img-responsive', src='/user/image/view/#{photoName}', id='target')
form(name='croppedImageForm', method='post', enctype='multipart/form-data', id='croppedImageForm')
input(type='hidden', name='refNumber', id='refNumber', value='#{refNumber}')
input(type='hidden', id='encodedImageValue', name='croppedImage')
br
input(type='submit', value='Upload Cropped Image', id='submitCroppedImage' data-role='button')
The darkroom.js is attached to the figure element using this piece of javascript.
new Darkroom('#target', {
// Canvas initialization size
minWidth: 160,
minHeight: 160,
maxWidth: 900,
maxHeight: 900,
});
});
Once you follow the STEP 1, STEP 2 and finally STEP 3 the base64 value of the cropped region is stored in under figure element see the console log shown in the screenshot below:
I then have a piece of javascript that is triggered when the Upload Cropped Image is clicked and it then copy/paste the base64 value of the img from figure into the input element with id encodedImageValue and it then submit it to the server. The javascript function is as follow:
$("#submitCroppedImage").click(function() {
var img = $('#imageContainer img');
var imgSrc = img.attr('src');
if(imgSrc !== undefined && imgSrc.indexOf('base64') > 0) {
$('#encodedImageValue').val(img.attr('src'));
$.ajax({
type: "POST",
url: "/user/image/cropped/submit",
data: $('#croppedImageForm').serialize(),
success: function(res, status, xhr) {
alert('The CROPPED image is UPLOADED.');
},
error: function(xhr, err) {
console.log('There was some error.');
}
});
} else {
alert('Please follow the steps correctly.');
}
});
Here is a screenshot of POST request with base64 field as its body
The post request is mapped to the following route handler in Express app:
router.post('/user/image/cropped/submit',
multerUploads,
function(req, res) {
var photoName = null;
var refNumber = req.body.refNumber;
var base64Data = req.body.croppedImage.replace(/^data:image\/png;base64,/, "");
fs.writeFile("./uploads/cropped-" + 'profile_image.png', base64Data, 'base64',
function(err) {
logger.info ("Saving image to disk ...");
res.status(200).send();
});
});
I have the following .js files relating to awesome Fabric.js and darkroom.js
script(src='/static/js/jquery.min.js')
script(src='/static/js/bootstrap.min.js')
//get the js files from darkroom.js github
script(src='/static/js/fabric.js')
script(src='/static/js/darkroom.js')
script(src='/static/js/plugins/darkroom.crop.js')
script(src='/static/js/plugins/darkroom.history.js')
script(src='/static/js/plugins/darkroom.save.js')
link(href='/static/css/darkroom.min.css', rel='stylesheet')
link(href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css', rel='stylesheet')
//get this from darkroom.js github
link(href='/static/css/page.css', rel='stylesheet')
Lastly, also copy the svg icons for selecting, cropping, saving, etc (from darkroo.js github page).

The easy way to convert an URL Image to Base64 or Blob in Javascript/Jquery

I'm working on an offline mode for a simple application, and I'm using Indexeddb (PounchDB as a library), I need to convert an Image to Base64 or BLOB to be able to save it.
I have tried this code which works for only one Image (the Image provided) I don't know why it doesn't work for any other image:
function convertImgToBase64URL(url, callback, outputFormat){
var img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function(){
var canvas = document.createElement('CANVAS'),
ctx = canvas.getContext('2d'), dataURL;
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
dataURL = canvas.toDataURL(outputFormat);
callback(dataURL);
canvas = null;
};
img.src = url;
}
convertImgToBase64URL('http://upload.wikimedia.org/wikipedia/commons/4/4a/Logo_2013_Google.png', function(base64Img){
alert('it works');
$('.output').find('img').attr('src', base64Img);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="output"> <img> </div>
It works fine, but only with one Image, If I try any other Image it doesn't work
The image you're using has CORS headers
Accept-Ranges : bytes
Access-Control-Allow-Origin : * // this one
Age : 69702
Connection : keep-alive
which is why it can be modified in a canvas like that.
Other images that don't have CORS headers are subject to the same-origin policy, and can't be loaded and modified in a canvas in that way.
I need to convert Image URL to Base64
See
Understanding the HTML5 Canvas image security rules
Browser Canvas CORS Support for Cross Domain Loaded Image Manipulation
Why does canvas.toDataURL() throw a security exception?
One possible workaround would be to utilize FileReader to convert img , or other html element to a data URI of html , instead of only img src; i.e.g., try opening console at jsfiddle http://jsfiddle.net/guest271314/9mg5sf7o/ , "right-click" on data URI at console, select "Open link in new tab"
var elem = $("div")[0].outerHTML;
var blob = new Blob([elem], {
"type": "text/html"
});
var reader = new FileReader();
reader.onload = function (evt) {
if (evt.target.readyState === 2) {
console.log(
// full data-uri
evt.target.result
// base64 portion of data-uri
, evt.target.result.slice(22, evt.target.result.length));
// window.open(evt.target.result)
};
};
reader.readAsDataURL(blob);
Another possible workaround approach would be to open image in new tab , window ; alternatively an embed , or iframe element ; utilize XMLHttpRequest to return a Blob , convert Blob to data URI
var xhr = new XMLHttpRequest();
// load `document` from `cache`
xhr.open("GET", "", true);
xhr.responseType = "blob";
xhr.onload = function (e) {
if (this.status === 200) {
// `blob` response
console.log(this.response);
var reader = new FileReader();
reader.onload = function(e) {
// `data-uri`
console.log(e.target.result);
};
reader.readAsDataURL(this.response);
};
};
xhr.send();
Do you need something like this?
http://www.w3docs.com/tools/image-base64

Getting an image width and height from Phonegap before upload

This should be easy but I can't find the answer. I need to get the width and height of an image, grabbed via a fileURI in Phonegap, before uploading it to our server. Certainly there's got to be some html5 / Phonegap magic that will do this before uploading. Here is some really reduced code to show where I'm at:
function got_image(image_uri) {
// Great, we've got the URI
window.resolveLocalFileSystemURI(image_uri, function(fileEntry) {
// Great, now we have a fileEntry object.
// How do we get the image width and height?
})
}
// Get the image URI using Phonegap
navigator.camera.getPicture(got_image, fail_function, some_settings)
Any idea what the missing piece is? I wish I had Simon MacDonald or Shazron on speed dial, but I do not.
Here is a proper solution.
Pass this function an imageURI. URIs are returned from both of PhoneGap's getPicture() methods.
Like this:
get_image_size_from_URI(imageURI)
function get_image_size_from_URI(imageURI) {
// This function is called once an imageURI is rerturned from PhoneGap's camera or gallery function
window.resolveLocalFileSystemURI(imageURI, function(fileEntry) {
fileEntry.file(function(fileObject){
// Create a reader to read the file
var reader = new FileReader()
// Create a function to process the file once it's read
reader.onloadend = function(evt) {
// Create an image element that we will load the data into
var image = new Image()
image.onload = function(evt) {
// The image has been loaded and the data is ready
var image_width = this.width
var image_height = this.height
console.log("IMAGE HEIGHT: " + image_height)
console.log("IMAGE WIDTH: " + image_width)
// We don't need the image element anymore. Get rid of it.
image = null
}
// Load the read data into the image source. It's base64 data
image.src = evt.target.result
}
// Read from disk the data as base64
reader.readAsDataURL(fileObject)
}, function(){
console.log("There was an error reading or processing this file.")
})
})
}
The following code solves the same problem for me today (tested on iOS):
function got_image(image_uri)
{
$('<img src="'+image_uri+'"/>').on('load',function(){
alert(this.naturalWidth +" "+ this.naturalHeight);
});
}
Hope it helps!
I think you can use target width and height of getPicture() to limit the maximum dimension.
If you need to send them to server, I think server code would help to get those dimension.
If you really need to get those dimension by js, you can
var img = new Image;
img.onload = function(){
myCanvasContext.drawImage(img,0,0);
};
img.src = "data:YOUR_BASE64_DATA";
And you can do it with img
1.Phonegap supply a way to specific the size of the image you choose
Click here to see the options
2.If you need to know the original size and do not want to specific, you may try the code below:
function got_image(image_uri) {
window.resolveLocalFileSystemURI(image_uri, function(fileEntry) {
fileEntry.file(function (fileObj) {
var fileName = fileObj.fullPath;
var originalLogo = new Image();
originalLogo.src =fileName;
//get the size by: originalLogo.width,originalLogo.height
});
})
}
one way you can do this is by using the MediaFile from the Capture API.
var mediaFile = new MediaFile("myfile.jpg", "/path/to/myfile.jpg");
mediaFile.getFormatData(function(data) {
console.log("width: " data.width);
console.log("height: " data.height);
});
Should be a lot less code than the current accepted solution.

Firefox issue with jquery.post

I have a function which retrieves a base64 encoded image from a controller and displays the image in a new window. It is working perfectly in Chrome, but fails in Firefox with the errors 'not well-formed' and 'Request method 'GET' not supported'. I don't understand why this would be the case given that I am using post, not get...
The relevant code section is:
function showPicture(label) {
$.post( 'picture-view', { labelname: label }, function(ImageSrc) {
var img = new Image;
img.src = "data:image/png;base64," + ImageSrc;
img.style.position = 'fixed';
img.style.left = '0';
img.style.top = '0';
img.onload = function() {
var newWindow = window.open("", label,"scrollbars=0, toolbar=0, width="+myImage.width+", height="+myImage.height);
creativeWindow.document.documentElement.innerHTML='';
creativeWindow.document.documentElement.appendChild(img);
}​
});}
Edit Also, any recommendations for accessible books or websites to learn more about this material would be gratefully received.
Edit #2 I should also add that when I tried writing img.src to the console it came out as [objectXMLDocument].
Edit #3 I have tracked the error down to the fact that the ImageSrc being returned should be a String but is being interpreted as an XML object. The fact that it contains back slashes is causing the 'not well-formed' error. My question is now: how do I get $.post or $.ajax to return a String in Firefox (they already do so in Chrome).
Id probably try using .ajax instead. It'll allow you to offer fallback content too.
Rather than sending the images using post, why not host the images on your server and load the images by src. It will be a much cleaner approach.
For example
$.ajax({
type: "POST",
data: "",
url: yourURL,
error: function() {
//The call was unsuccessful
//Place fallback content/message here
},
success: function(data) {
//The call was successful
//Open new window
var newWindow = window.open("", label,"scrollbars=0, toolbar=0, width="+myImage.width+", height="+myImage.height);
creativeWindow.document.documentElement.innerHTML='';
//Create the image
var image = document.createElement("img");
image.src = data;
//Attach image to new window
creativeWindow.document.documentElement.appendChild(image);
}
});
In the interest of not leaving an unanswered question...
I ended up using a lightbox to display the image rather than generating a new window.

Categories

Resources