63

Many libraries I have found, like Jcrop, do not actually do the cropping, it only creates an image cropping UI. It then depends on the server doing the actual cropping.

How can I make the image cropping client-side by using some HTML5 feature without using any server-side code.

If yes, are there some examples or hints?

Undo
  • 25,519
  • 37
  • 106
  • 129
Lorraine Bernard
  • 13,000
  • 23
  • 82
  • 134
  • 3
    Possible duplicates: 1. [Javascript crop image client-side](http://stackoverflow.com/questions/6848121/javascript-crop-image-client-side) (exactly what you're asking; however, top answer says it can't be done, but this is wrong). 2. [Copy and crop images in Javascript](http://stackoverflow.com/questions/4200374/copy-and-crop-images-in-javascript) (slightly different use case, but the top answer is exactly what you want) – apsillers Oct 04 '12 at 14:29
  • https://github.com/foliotek/croppie – Tom M Sep 20 '18 at 15:36

4 Answers4

45

Yes, it can be done. It is based on the new HTML5 "download" attribute of anchor tags. The flow should be something like this:

  1. load the image
  2. draw the image into a canvas with the crop boundaries specified
  3. get the image data from the canvas and make it a href attribute for an anchor tag in the DOM
  4. add the download attribute (download="desired-file-name") to that a element That's it. all the user has to do is click your "download link" and the image will be downloaded to his pc.

I'll come back with a demo when I get the chance.

Here's the live demo as I promised. It takes the JSFiddle logo and crops 5px of each margin. The code looks like this:

var img = new Image();
img.onload = function(){
    var cropMarginWidth = 5,
        canvas = $('<canvas/>')
                    .attr({
                         width: img.width - 2 * cropMarginWidth,
                         height: img.height - 2 * cropMarginWidth
                     })
                    .hide()
                    .appendTo('body'),
        ctx = canvas.get(0).getContext('2d'),
        a = $('<a download="cropped-image" title="click to download the image" />'),
        cropCoords = {
            topLeft : {
                x : cropMarginWidth,
                y : cropMarginWidth
            },
            bottomRight :{
                x : img.width - cropMarginWidth,
                y : img.height - cropMarginWidth
            }
        };

    ctx.drawImage(img, cropCoords.topLeft.x, cropCoords.topLeft.y, cropCoords.bottomRight.x, cropCoords.bottomRight.y, 0, 0, img.width, img.height);
    var base64ImageData = canvas.get(0).toDataURL();

    a
        .attr('href', base64ImageData)
        .text('cropped image')
        .appendTo('body');

    a
        .clone()
        .attr('href', img.src)
        .text('original image')
        .attr('download','original-image')
        .appendTo('body');

    canvas.remove();
}
img.src = 'some-image-src';

Forgot to mention: of course there is a downside :(. Because of the same-origin policy that is applied to images too, if you want to access an image's data (through the canvas method toDataUrl). So you would still need a server-side proxy that would serve your image as if it were hosted on your domain.

Although I can't provide a live demo for this (for security reasons), here is a PHP sample code that solves the same-origin policy:

file proxy.php:

$imgData = getimagesize($_GET['img']);
header("Content-type: " . $imgData['mime']);
echo file_get_contents($_GET['img']);

This way, instead of loading the external image direct from it's origin:

img.src = 'http://example.com/imagefile.png';

You can load it through your proxy:

img.src = 'proxy.php?img=' + encodeURIComponent('http://example.com/imagefile.png');

And here's a sample PHP code for saving the image data (base64) into an actual image:

file save-image.php:

$data = preg_replace('/data:image\/(png|jpg|jpeg|gif|bmp);base64/','',$_POST['data']);
$data = base64_decode($data);
$img = imagecreatefromstring($data);

$path = 'path-to-saved-images/';
// generate random name
$name  = substr(md5(time()),10);
$ext = 'png';
$imageName = $path.$name.'.'.$ext;

// write the image to disk
imagepng($img,  $imageName);
imagedestroy($img);
// return the image path
echo $imageName;

All you have to do then is post the image data to this file and it will save the image to disc and return you the existing image filename.

Of course all this might feel a bit complicated, but I wanted to show you that what you're trying to achieve is possible.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
gion_13
  • 41,171
  • 10
  • 96
  • 108
  • +1 It will be great to have a demo. I will try to do that too. But it is strange there are not such example on the web yet. – Lorraine Bernard Oct 04 '12 at 14:55
  • I did, thanks. Do you think it is reasonable/achievable to use https://github.com/tapmodo/Jcrop with your example in order to upload the image on the server? Reading your `Update II`it seems I can't. Am I right? – Lorraine Bernard Oct 04 '12 at 15:24
  • Yes, it can be done. If you have the image data, all you have to do is post it to a server side page that writes it into an image file. The only tricky part (and it's not even that tricky) is to proxy the external images with a proxy.ex : instead of `http://domain.com/image.png` use `proxy.php?img=http://domain.com/image.png`. I dont know if I can make a demo for this, but you should know that it's totally doable :) – gion_13 Oct 05 '12 at 08:00
  • Your example gives the error: SecurityError: The operation is insecure. (img.onload()) – Huseyin Yagli May 09 '16 at 08:50
7

The Pixastic library does exactly what you want. However, it will only work on browsers that have canvas support. For those older browsers, you'll either need to:

  1. supply a server-side fallback, or
  2. tell the user that you're very sorry, but he'll need to get a more modern browser.

Of course, option #2 isn't very user-friendly. However, if your intent is to provide a pure client-only tool and/or you can't support a fallback back-end cropper (e.g. maybe you're writing a browser extension or offline Chrome app, or maybe you can't afford a decent hosting provider that provides image manipulation libraries), then it's probably fair to limit your user base to modern browsers.

EDIT: If you don't want to learn Pixastic, I have added a very simple cropper on jsFiddle here. It should be possible to modify and integrate and use the drawCroppedImage function with Jcrop.

Robert MacLean
  • 38,975
  • 25
  • 98
  • 152
apsillers
  • 112,806
  • 17
  • 235
  • 239
  • Hi @apsillers, thanks for your answer. Are you sure that [this demo](http://www.pixastic.com/lib/docs/actions/crop/) is cropping and resize the image? I mean after the crop action I cannot download the cropped image but I can download just the original one. – Lorraine Bernard Oct 04 '12 at 14:49
  • 1
    The cropped image is drawn to a canvas. You use `toDataURL` to get the base64-encoded image data out of the canvas. If you type in `javascript:window.open($("#demoimage")[0].toDataURL())` into your URL bar after you do the crop, you'll see the cropped image itself. – apsillers Oct 04 '12 at 15:19
  • (Note: if you *paste* that code in your address bar, your browser may strip out the beginning `javascript:` part; be sure to add it back in.) – apsillers Oct 04 '12 at 15:25
  • +1 thanks it works :). Anyway I would like to use a js plugin which is available on github and Pixastic it does not :( . Am I right? – Lorraine Bernard Oct 04 '12 at 15:32
  • 2
    Are you looking for https://github.com/jseidelin/pixastic? – apsillers Oct 04 '12 at 15:38
  • 2
    unfortunately it looks like the Pixastic project is dead – Cfreak Jul 05 '16 at 21:07
3

#change-avatar-file is a file input #change-avatar-file is a img tag (the target of jcrop) The "key" is FR.onloadend Event https://developer.mozilla.org/en-US/docs/Web/API/FileReader

$('#change-avatar-file').change(function(){
        var currentImg;
        if ( this.files && this.files[0] ) {
            var FR= new FileReader();
            FR.onload = function(e) {
                $('#avatar-change-img').attr( "src", e.target.result );
                currentImg = e.target.result;
            };
            FR.readAsDataURL( this.files[0] );
            FR.onloadend = function(e){
                //console.log( $('#avatar-change-img').attr( "src"));
                var jcrop_api;

                $('#avatar-change-img').Jcrop({
                    bgFade:     true,
                    bgOpacity: .2,
                    setSelect: [ 60, 70, 540, 330 ]
                },function(){
                    jcrop_api = this;
                });
            }
        }
    });
-1

If you will still use JCrop, you will need only this php functions to crop the file:

$img_src = imagecreatefromjpeg($src);
$img_dest = imagecreatetruecolor($new_w,$new_h);
imagecopyresampled($img_dest,$img_src,0,0,$x,$y,$new_w,$new_h,$w,$h);
imagejpeg($img_dest,$dest);

client side:

jQuery(function($){

    $('#target').Jcrop({
    onChange:   showCoords,
    onSelect:   showCoords,
    onRelease:  clearCoords
    });

});

var x,y,w,h; //these variables are necessary to crop
function showCoords(c)
{
    x = c.x;
    y = c.y;
    w = c.w;
    h = c.h;
};
function clearCoords()
{
    x=y=w=h=0;
}