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

前端 未结 2 706
花落未央
花落未央 2020-11-30 06:04

I am working on a component in which there is file-upload HTML control, upon selecting the image using the control, the image would be rendered on the HTML5 Canvas element

相关标签:
2条回答
  • 2020-11-30 06:32

    Seahorsepip's answer is fantastic. I made a lot of improvements on the non-fallback answer.

    http://jsfiddle.net/w1Lh4w2t/

    I would recommend not doing that strange hidden png thing, when an Image object works just as well (so long as we're not supporting fallbacks).

    var jcrop_api;
    var canvas;
    var context;
    var image;
    var prefsize;
    

    Though even then we are, you're better off getting that data out of the canvas at the end and putting it in that field only at the end.

    function loadImage(input) {
      if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function(e) {
          image = new Image();
          image.src = e.target.result;
          validateImage();
        }
        reader.readAsDataURL(input.files[0]);
      }
    }
    

    But, if you want more functions than just crop, if we attach the jcrop to an inserted canvas (which we destroy with the jcrop on refresh). We can easily do anything we can do with a canvas, then validateImage() again and have the updated image visible in place.

    function validateImage() {
      if (canvas != null) {
        image = new Image();
        image.src = canvas.toDataURL('image/png');
      }
      if (jcrop_api != null) {
        jcrop_api.destroy();
      }
      $("#views").empty();
      $("#views").append("<canvas id=\"canvas\">");
      canvas = $("#canvas")[0];
      context = canvas.getContext("2d");
      canvas.width = image.width;
      canvas.height = image.height;
      context.drawImage(image, 0, 0);
      $("#canvas").Jcrop({
        onSelect: selectcanvas,
        onRelease: clearcanvas,
        boxWidth: crop_max_width,
        boxHeight: crop_max_height
      }, function() {
        jcrop_api = this;
      });
      clearcanvas();
    }
    

    Then on submit we submit any pending operations, like applyCrop() or applyScale(), adding data into hidden fields for fallback stuff, if we have those things needed. We then have a system we can easily just modify the canvas, in any way, then when we submit the canvas data gets sent properly.

    function applyCrop() {
      canvas.width = prefsize.w;
      canvas.height = prefsize.h;
      context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width, canvas.height);
      validateImage();
    }
    

    The canvas is added to a div views.

     <div id="views"></div>
    

    To catch the attached file in PHP (drupal), I used something like:

        function makeFileManaged() {
            if (!isset($_FILES['croppedfile']))
                return NULL;
            $path = $_FILES['croppedfile']['tmp_name'];
            if (!file_exists($path))
                return NULL;
            $result_filename = $_FILES['croppedfile']['name'];
            $uri = file_unmanaged_move($path, 'private://' . $result_filename, FILE_EXISTS_RENAME);
            if ($uri == FALSE)
                return NULL;
            $file = File::Create([
                        'uri' => $uri,
            ]);
            $file->save();
            return $file->id();
        }
    
    0 讨论(0)
  • 2020-11-30 06:32

    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.

    0 讨论(0)
提交回复
热议问题