piechuckerr piechuckerr - 5 months ago 325
Javascript Question

How should I crop the image at client side using jcrop and upload it?

What I am trying to do is select the image using html fileupload control, crop the selection using jcrop, crop image at client side and upload the image. Here is fiddle link https://jsfiddle.net/govi20/spmc7ymp/

Id's used in code:


target for jcrop image

photograph fileupload control

preview canvas preview

clear_selection button the clear the selected area




setting up the jcrop.

<script type="text/javascript">
jQuery(function($){

var api;

$('#target').Jcrop({
// start off with jcrop-light class
bgOpacity: 0.5,
keySupport: false,
bgColor: 'black',
minSize:[240,320],
maxSize:[480,640],
onChange : updatePreview,
onSelect : updatePreview,
height:160,
width:120,
addClass: 'jcrop-normal'
},function(){
api = this;
api.setSelect([0,0,240,320]);
api.setOptions({ bgFade: true });
api.ui.selection.addClass('jcrop-selection');
});

});


for clearing the selection.

jQuery('#clear_selection').click(function(){
$('#target').Jcrop({

setSelect: [0,0,0,0],
});
});


to display uploaded file in jcrop

function readURL(input) {

if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#target').attr('src', e.target.result);
setProperties();
}
reader.readAsDataURL(input.files[0]);
}
}

function setProperties(){
$('#target').Jcrop({
setSelect: [0,0,240,320]
});
}
$("#photograph").change(function(){
readURL(this);
});


to crop the image and assign it to canvas(got this from one of the threads on stackoverflow)

function make_base() {
console.log("make_base called");
var base_image = new Image();
base_image.src = '';
base_image.onload = function () {
context.drawImage(base_image, 0, 0);
}
}

var canvas = document.getElementById('preview'),
context = canvas.getContext('2d');

make_base();
function updatePreview(c) {
console.log("called");
if(parseInt(c.w) > 0) {
// Show image preview
var imageObj = $("#target")[0];
var canvas = $("#preview")[0];
var context = canvas.getContext("2d");
context.drawImage(imageObj, c.x, c.y, c.w, c.h, 0, 0, canvas.width, canvas.height);
}
};




Issues I am facing:


  1. updatePreview function is not getting called on selection, hence no canvas is visible.

  2. crop selection is not draggable (I am using bootstrap css).

  3. Canvas is HTML5 element, that means client should have html5 compatible browser, so how can I remove this condition.


Answer

Here's basic html 5 code:

https://jsfiddle.net/zm7e0jev/

This code crops the image, shows a preview and sets the value of an input element to the base64 encoded cropped image.

You can fetch the image file in php the following way:

//File destination
$destination = "/folder/cropped_image.png";
//Get convertable base64 image string
$image_base64 = $_POST["png"];
$image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
$image_base64 = str_replace(" ", "+", $image_base64);
//Convert base64 string to image data
$image = base64_decode($image_base64);
//Save image to final destination
file_put_contents($destination, $image);

Submitting base64 image string as a post variable has it's server post size limits and base64 encoding makes the cropped image file size even bigger (~33%) then the raw data of the cropped image would be which makes the upload take even longer.

To set the post size limit: What is the size limit of a post request?

Keep in mind that an increased post size limit can be abused for a DoS attack as example.

Instead I suggest converting the base64 cropped image to a data blob and then add it to the form on submit as a file:

https://jsfiddle.net/g3ysk6sf/

Then you can fetch the image file in php the following way:

//File destination
$destination = "/folder/cropped_image.png";
//Get uploaded image file it's temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);

Update:

FormData() is only partially support in IE10 and not supported in older versions of IE

So I suggest sending the base64 string as a fallback, though this will cause problems with bigger images so it needs to check the filesize and show an error popup when the image is above a specific size.

I'll post an update with the fallback code below when I've got it working.

Update 2:

I added a fallback for IE10 and below:

https://jsfiddle.net/oupxo3pu/

The only limitation is the image size that can be submitted when using IE10 and below, in case the image size is too big the js code will throw an error. The maximum size to work for post values is different between each server, the js code has a variable to set the maximum size.

The php code below is adapted to work with above fallback:

//File destination
$destination = "/folder/cropped_image.png";
if($_POST["png"]) {//IE10 and below
    //Get convertable base64 image string
    $image_base64 = $_POST["png"];
    $image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
    $image_base64 = str_replace(" ", "+", $image_base64);
    //Convert base64 string to image data
    $image = base64_decode($image_base64);
    //Save image to final destination
    file_put_contents($destination, $image);
} else if($_FILES["cropped_image"]) {//IE11+ and modern browsers
    //Get uploaded image file it's temporary name
    $image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
    //Move temporary file to final destination
    move_uploaded_file($image_tmp_name, $destination);
}
There is no fallback code for the canvas element yet, I'm looking into it.
The post size limitation in the fallback for older browsers is one of the reasons I dropped support for older browsers myself.

Update 3:

The fallback I recommend for the canvas element in IE8:

http://flashcanvas.net/

It supports all the canvas functions the cropping code needs.

Keep in mind it requires flash. There is a canvas fallback (explorercanvas) that does not require flash but it does not support the function toDataURL() which we need to save our cropped image.