Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 345
buydadip Avatar asked Sep 21 '25 02:09

buydadip


2 Answers

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.

like image 168
EMX Avatar answered Sep 22 '25 16:09

EMX


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')

enter image description here

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')

enter image description here

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:

enter image description here

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

enter image description here

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).

like image 27
Raf Avatar answered Sep 22 '25 15:09

Raf