Cropping a profile picture in Node JS - javascript

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

Related

How to generate "screenshot" of html div with external images?

I have a HTML div with external images. (The following is an example, but in the actual case I am using Amazon S3, so downloading and storing the image on the same server is not an option) Currently I am using html2canvas to convert the div to image. However, the external image is always replaced by a blank space.
The code I use to capture the image:
$(function() {
$("#btnSave").click(function() {
html2canvas($("#widget"), {
onrendered: function(canvas) {
theCanvas = canvas;
document.body.appendChild(canvas);
// Convert and download as image
Canvas2Image.saveAsPNG(canvas);
$("#img-out").append(canvas);
}
});
});
});
Edited: jsfiddle: https://jsfiddle.net/0y2zxxL8/3/
I may use other library. I may also do that in backend. (I am using PHP + laravel 5 for backend) Is there a way I can generate a "screenshot" of the HTML div with external images?
Update The current answer are working after editing. Yet, for my actual use, there will be multiple image with their position set by the user by drag and drop. I can still get the position, but it would be better for me if it is possible to not set the position specifically.
Your JSFiddle given ReferenceError: Canvas2Image is not defined while hit on 'Save PNG' button. So check your code(not fiddle) for same error.
UPDATE
See this JSFiddle example as reference. May this one will help you.
UPDATE 2
I place some code into your one, check it out. Hope this will be your solution..!
Working result with FF v44 and its working. Taken snap with JSFiddle code
$(function() {
$("#btnSave").click(function() {
html2canvas($("#widget"), {
onrendered: function(canvas) {
var context=canvas.getContext("2d"); // returns the 2d context object
var img=new Image() //creates a variable for a new image
img.src= "http://lorempixel.com/400/200/"; // specifies the location of the image
context.drawImage(img,0,50); // draws the image at the specified x and y location
// Convert and download as image
theCanvas = canvas;
document.body.appendChild(canvas);
// Convert and download as image
Canvas2Image.saveAsPNG(canvas);
$("#img-out").append(canvas);
}
});
});
});
UPDATE 3 (12/29/2016) JSFiddle
After research, found that the problem is because we are only assigning the image source which is only pass message to the browser to retrieve the data.
Further image element is may be not really accessible with the browser when click to draw canvas from it. We are just tell code to load it and it's done.
Change code to solve OP's facing chrome issue like below:
img.onload = function() {
context.drawImage(img, 0, 50);// draws the image at the specified x and y location
}
UPDATE 4 JSFiddle
Make image position dynamic based on OP requirement.
onrendered is no longer working..
i solved this problem by adding "allowTaint" option
{ allowTaint: true }
2018 solution:
html2canvas(document.getElementById('result'), { allowTaint: true }).then(function (canvas) {
document.body.appendChild(canvas);
});
You may try to scan the image source for external urls and change them to your website and load them with php.
Something like
$(function() {
var imageproxy = "http://example.com/imageproxy.php"; //Change this
var mydomain = new RegExp(location.host);
$("#btnSave").click(function() {
$("#widget").find('img').each(function(){
var img_src = $(this).attr('src');
if(!mydomain.test(img_src)){
$(this).attr('src', imageproxy + "?url=" + encodeURIComponent(img_src));
}
});
html2canvas($("#widget"), {
onrendered: function(canvas) {
var link = $('<a target="_blank">Download</a>');
link.attr('download', 'test.png');
link.attr('href', canvas.toDataURL());
$("#btnSave").after(link);
}
});
});
});
imageproxy.php
<?php
if(!empty($_GET['url'])){
$url = urldecode($_GET['url']);
$img_type = "image/jpeg";
$chunks = explode('.', $url);
if(is_array($chunks) && count($chunks) > 1){
$ext = end($chunks);
switch ($ext){
case 'jpg':
case 'jpeg':
$img_type = "image/jpeg";
break;
case 'png':
$img_type = "image/png";
break;
case 'gif':
$img_type = "image/gif";
break;
}
}
$img = file_get_contents($url);
header ("Content-Type: $img_type");
echo $img;
}
note: php script is very simple, image detection is not working 100%, this is just ti give you an idea. If you use some php image libs to load and get image type you can do this just with few lines of code. you may also try with "php readfile".

jCrop resize with correct coords when changing image

Hoping this doesn't get flagged as a duplicate because none of the other q/as on SO have helped me fix this, I think I need a more specific line of help.
I have a profile page on my site that allows the user to change their profile picture without page reloads (via AJAX / jQuery).
This all works fine. The user opens the "Change Profile Picture" modal, selects a file to upload and presses "Crop this image". When this button is pressed, it uploads a file to the website, using the typical way of sending the file and formData (which I append the file data to).
It gets sent backend with the following jQuery:
// Upload the image for cropping (Crop this Image!)
$("#image-upload").click(function(){
// File data
var fileData = $("#image-select").prop("files")[0];
// Set up a form
var formData = new FormData();
// Append the file to the new form for submission
formData.append("file", fileData);
// Send the file to be uploaded
$.ajax({
// Set the params
cache: false,
contentType: false,
processData: false,
// Page & file information
url: "index.php?action=uploadimage",
dataType: "text",
type: "POST",
// The data to send
data: formData,
// On success...
success: function(data){
// If no image was returned
// "not-image" is returned from the PHP script if we return it in case of an error
if(data == "not-image"){
alert("That's not an image, please upload an image file.");
return false;
}
// Else, load the image on to the page so we don't need to reload
$(profileImage).attr("src", data);
// If the API is already set, then we should apply a new image
if(jCropAPI){
jCropAPI.setImage(data + "?" + new Date().getTime());
}
// Initialise jCrop
setJCrop();
//$("#image-profile").show();
$("#send-coords").show();
}
})
});
setJcrop does the following
function setJCrop(){
// Get width / height of the image
var width = profileImage.width();
var height = profileImage.height();
// Var containing the source image
var imgSource = profileImage.attr("src");
// New image object to work on
var image = new Image();
image.src = imgSource;
// The SOURCE (ORIGINAL) width / height
var origWidth = image.width;
var origHeight = image.height;
// Set up the option to jCrop it
$(profileImage).Jcrop({
onSelect: setCoords,
onChange: setCoords,
setSelect: [0, 0, 51, 51],
aspectRatio: 1, // This locks it to a square image, so it fits the site better
boxWidth: width,
boxHeight: height, // Fixes the size permanently so that we can load new images
}, function(){jCropAPI = this});
setOthers(width, height, origWidth, origHeight);
}
And once backend, it does the following:
public function uploadImage($file){
// See if there is already an error
if(0 < $file["file"]["error"]){
return $file["file"]["error"] . " (error)";
}else{
// Set up the image
$image = $file["file"];
$imageSizes = getimagesize($image["tmp_name"]);
// If there are no image sizes, return the not-image error
if(!$imageSizes){
return "not-image";
}
// SIZE LIMIT HERE SOON (TBI)
// Set a name for the image
$username = $_SESSION["user"]->getUsername();
$fileName = "images/profile/$username-profile-original.jpg";
// Move the image which is guaranteed a unique name (unless it is due to overwrite), to the profile pictures folder
move_uploaded_file($image["tmp_name"], $fileName);
// Return the new filename
return $fileName;
}
}
Then, the user selects their area on the image with the selector and pressed "Change Profile Picture" which does the following
// Send the Coords and upload the new image
$("#send-coords").click(function(){
$.ajax({
type: "POST",
url: "index.php?action=uploadprofilepicture",
data: {
coordString: $("#coords").text() + $("#coords2").text(),
imgSrc: $("#image-profile").attr("src")
},
success: function(data){
if(data == "no-word"){
alert("Can not work with this image type, please try with another image");
}else{
// Append a date to make sure it reloads the image without using a cached version
var dateNow = new Date();
var newImageLink = data + "?" + dateNow.getTime();
$("#profile-picture").attr("src", newImageLink);
// Hide the modal
$("#profile-picture-modal").modal("hide");
}
}
});
})
The backend is:
public function uploadProfilePicture($coordString, $imgSrc){
// Target dimensions
$tarWidth = $tarHeight = 150;
// Split the coords in to an array (sent by a string that was created by JS)
$coordsArray = explode(",", $coordString);
//Set them all from the array
$x = $coordsArray[0];
$y = $coordsArray[1];
$width = $coordsArray[2];
$height = $coordsArray[3];
$newWidth = $coordsArray[4];
$newHeight = $coordsArray[5];
$origWidth = $coordsArray[6];
$origHeight = $coordsArray[7];
// Validate the image and decide which image type to create the original resource from
$imgDetails = getimagesize($imgSrc);
$imgMime = $imgDetails["mime"];
switch($imgMime){
case "image/jpeg":
$originalImage = imagecreatefromjpeg($imgSrc);
break;
case "image/png":
$originalImage = imagecreatefrompng($imgSrc);
break;
default:
return "no-work";
}
// Target image resource
$imgTarget = imagecreatetruecolor($tarWidth, $tarHeight);
$img = imagecreatetruecolor($newWidth, $newHeight);
// Resize the original image to work with our coords
imagecopyresampled($img, $originalImage, 0, 0, 0, 0,
$newWidth, $newHeight, $origWidth, $origHeight);
// Now copy the CROPPED image in to the TARGET resource
imagecopyresampled(
$imgTarget, // Target resource
$img, // Target image
0, 0, // X / Y Coords of the target image; this will always be 0, 0 as we do not want any black nothingness
$x, $y, // X / Y Coords (top left) of the target area
$tarWidth,
$tarHeight, // width / height of the target
$width,
$height // Width / height of the source image crop
);
$username = $_SESSION["user"]->getUsername();
$newPath = "images/profile/$username-profile-cropped.jpg";
// Create that shit!
imagejpeg($imgTarget, $newPath);
// Return the path
return $newPath;
}
So basically this the returns the path of the new file, which gets changed to the user's profile picture (same name every time) and uploaded live with a time appended after ? to refresh the image properly (no cache).
This all works fine, however if the user selects another image to upload, after already uploading one, the coords get all messed up (e.g. they go from 50 to 250) and they end up cropping a totally different part of the image, leaving most of it black nothing-ness.
Really sorry for the ridiculous amount of code that is in this question but I'd appreciate any help from people who might have worked around this before.
Some of the code may seem out of place but that's just me trying to debug it.
Thanks, and again, sorry for the size of this question.
-Edit-
My setCoords() and setOthers() functions look like so:
//Set the coords with this method, that is called every time the user makes / changes a selection on the crop panel
function setCoords(c){
$("#coords").text(c.x + "," + c.y + "," + c.w + "," + c.h + ",");
}
//This one adds the other parts to the second div; they will be concatenated in to the POST string
function setOthers(width, height, origWidth, origHeight){
$("#coords2").text(width + "," + height + "," + origWidth + "," + origHeight);
}
I have now resolved this issue.
The problem for me was that when using setJCrop(); - it was not re-loading the image. The reason for this is that the image uploaded and then loaded in to the JCrop window had the same name every time (username as a prefix, and then profile-cropped.jpg).
So to try and resolve this, I used the setImage method which loaded a full-sized image instead.
I got around this by setting the boxWidth / boxHeight params but they just left me with the issue of the coordinates being incorrect every time I loaded a new image in.
Turns out, it was loading the image from the cache every time, even when I was using new Image(); within jQuery.
To solve this, I have now used destroy(); on the jCropAPI and then re-initialised it every time, witout using setImage();
I set a max-width in the CSS on the image itself, which stopped it from being locked to a specific width.
The next problem was that every time I loaded an image a second time, it left the width / height of the old image there, which made the image look all skewed and wrong.
To solve this, I reset the width & height of the image that I use jCrop on, to "" with $(profileImage).css("width", ""); $(profileImage).css("height", ""); before re-setting the source of the image from the new uploaded image.
But I was still left with the issue of using the same name on the images, and then causing it to load from cache every time.
My solution to this was to add an "avatar" column in the database and save the image name in the Database each time. The image was named as $username-$time.jpg and $username-$time.jpg-cropped.jpg where $username is the username of the user (derp) and $time is simply time(); inside PHP.
This meant that every time I uploaded an image, it had a new name, so when any calls were made to this image, there was no cache of it.
Appending like imageName + ".jpg?" + new Date.getTime(); worked for some things but then when sending the image name backend, it didn't work properly, and deciding when to append it / not append it was a pain, and then one thing required it to be appended to force a re-load, but then when appended it didn't work properly, so I had to re-work it.
So the key: (TL;DR)
Don't use the same image name with jCrop, if you are loading a new image; upload an image with a different name and then refer to that one. Cache problems are a pain, and you can't really work around them properly without just using a new name every time, as this ensures that there will be absolutely no more problems (so long as the name is always unique).
Then, when you initialise jCrop, destroy the previous one beforehand if there is one. Use max-width instead of width on an image to stop it from locking the width, and re-set the width / height of the image if you're loading a new one in to the same <img> or <div>
Hope this helps somebody!
I used jcrop and I think this has happened to me. When there is a new image, you have to "reset" jcrop. Try something like this:
function resetJCrop()
{
if (jCropAPI) {
jCropAPI.disable();
jCropAPI.release();
jCropAPI.destroy();
}
}
$("#image-upload").click(function(){
success: function(data){
...
resetJCrop(); // RESETTING HERE
// If the API is already set, then we should apply a new image
if(jCropAPI){
jCropAPI.setImage(data + "?" + new Date().getTime());
}
// Initialise jCrop
setJCrop();
...
}
});
I can't remember the details about why I have to use disable() AND release() AND destroy() in my particular case. May be you can use only one of those. Just try it, and see if that works for you!

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

I am working on a component in which there is file-upload HTML control, upon selecting an image using the file-upload element, the image would be rendered on the HTML5 Canvas element.
Here is JSFiddle with sample code: https://jsfiddle.net/govi20/spmc7ymp/
id=target => selector for jcrop element
id=photograph => selector for fileupload element
id=preview => selector for canvas element
id=clear_selection => selector for a button which would clear the canvas
Third-party JS libraries used:
<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.Jcrop.js"></script>
<script src="./js/jquery.color.js"></script>
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');
});
});
clear canvas event which will be triggered on clear button click event:
jQuery('#clear_selection').click(function(){
$('#target').Jcrop({
setSelect: [0,0,0,0],
});
});
code that renders image on HTML5 Canvas:
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);
});
code to crop and render an image on the canvas:
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);
}
};
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);
}
}
Here are a bunch of issues I am facing with the above setup:
updatePreview function is not getting called on selection, hence the canvas is not getting rendered.
crop selection box is not draggable (I am using bootstrap CSS, I suspect it is due to missing/mismatching dependency).
Canvas is HTML5 element, which means the end-user must have an HTML5 compatible browser, I am working on an app that has millions of users. Forcing users to use the latest browser is not a feasible option. What should be the fallback mechanism here?
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.
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();
}

Output multiple files with GraphicsMagick

I have the following function set up which captures uploaded images, converts them and them writes them to the server. How do I go about renaming the converted file?
Currently the function takes the uploaded image and saves out one version of the image.
Goals:
Save out multiple versions of the uploaded image depending on image size.
Append filename with image size, eg. filename-400x400.jpg, filename-200x200.jpg
I'm not too sure how to go about the first one and the second, I think busboy is interfering with it, as filename in the function below includes to the extension. I need to be able to append the filename with the size before the extension. Can this be done with a regex?
Function:
// upload new user image
app.post('/api/users/user/upload_image', function (req, res) {
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename) {
console.log('\n\nUploading file: '.underline +filename .underline);
console.log('Uploaded\nConverting...');
var size = { width: 200, height: 200 };
gm(file,'www/uploads/' + filename)
.resize(size.width * 2, (size.height * 2) + '')
.thumbnail(size.width, size.height + '^')
.gravity('Center')
.extent(size.width, size.height)
.file(filename)
.profile('*')
.write('www/uploads/' + filename, function (err) {
console.log("Success!".bold);
});
});
req.busboy.on('finish', function () {
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
});
As far as creating multiple files goes, you can just duplicate the gm chain you have already, just use a different filename.
For the filename problem you could either just create your own filenames or use a regexp to insert the dimensions into the original filename. The latter you could use something like:
// changes "foo.jpg" to "foo-400x400.jpg"
filename.replace(/\.[^\.]+$/, '-400x400$&')

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.

Categories

Resources