I'm trying to create a thumbnail image for a larger image. I'm using Jcrop to get the co-ordinates of the crop but I can't turn this into a properly cropped thumbnail image. I've set up Jcrop properly and it's sending through x-y co-ordinates along with the size of the box but I can't work out how to crop from that.
What I have is the path to an image on my server, along with 4 co-ordinates and the width and height of the square box they create (I've locked the aspect ratio to 1:1 as I want square thumbnails). I then send this via Ajax to my PHP cropping script, but I cannot get it to crop the based on what is set.
This is what I have so far:
public function Crop($file, $crop) {
$height = $width = 180;
$ratio = $width / $height;
$pos = strrpos($file, '.');
$name = substr($file, 0, $pos);
$ext = strtolower(substr($file, $pos));
if( ! in_array($ext, array('.gif', '.jpg', '.jpeg', '.png'))) {
return 'INVALID_EXT';
}
// When loading the image we check to see if the first character in file is a slash, and if so remove it as the last character of root is a slash.
$src = ROOT . (in_array(substr($file, 0, 1), array('/', '\\')) ? substr($file, 1) : $file);
$srcRes = imagecreatefromstring(file_get_contents($src));
if( ! $srcRes) {
return 'INVALID_FILE';
}
$srcWidth = imagesx($srcRes);
$srcHeight = imagesy($srcRes);
$srcRatio = $srcWidth / $srcHeight;
$dstRes = imagecreatetruecolor($crop['w'], $crop['h']);
if($ext == '.gif') {
$dstBg = imagecolorallocate($dstRes, 0, 0, 0);
imagecolortransparent($dstRes, $dstBg);
} elseif($ext == '.png') {
$dstBg = imagecolorallocate($dstRes, 0, 0, 0);
imagecolortransparent($dstRes, $dstBg);
imagealphablending($dstRes, FALSE);
imagesavealpha($dstRes, TRUE);
}
$srcX = 0;
$srcY = 0;
if($srcRatio > $ratio) {
$tmpWidth = $srcHeight * $ratio;
$tmpHeight = $srcHeight;
$srcX = ($srcWidth - $tmpWidth) / 2;
$srcY = 0;
} else {
$tmpWidth = $srcWidth;
$tmpHeight = $srcWidth / $ratio;
$srcX = 0;
$srcY = ($srcHeight - $tmpHeight) / 2;
}
imagecopyresampled($dstRes, $srcRes, 0, 0, $crop['x'], $crop['y'], $crop['w'], $crop['h'], $tmpWidth, $tmpHeight);
$dst = ROOT . (in_array(substr($name, 0, 1), array('/', '\\')) ? substr($name, 1) : $name) . '-thumb' . $ext;
if($ext == '.gif') {
$try = imagegif($dstRes, $dst);
} elseif($ext == '.jpg' || $ext == '.jpeg') {
$try = imagejpeg($dstRes, $dst, 80);
} elseif($ext == '.png') {
$try = imagepng($newThumbImageResource, $dst);
}
if( ! $try) {
return 'CREATE_ERR';
}
return 'SUCCESS';
}
I've tried changing all sorts of things in imagecopyresampled
but cannot get it to crop as per what Jcrop is sending through. If I use $srcWidth
and $srcHeight
it maintains the aspect ratio of the original image and squishes it into the 180px square. But it doesn't seem to be resizing it or cropping it from the right place.
Can someone please help me work out what figures I should be using where? I've been banging my head against this all day.
EDIT
Here is the rest of the code. First the JavaScript that runs JCrop. It's triggered after an Ajax file upload to create a thumbnail of that image:
This is the Ajax function upload that at the end calls the cropper.
$(function () {
'use strict';
// Change this to the location of your server-side upload handler:
var url = '/eshop/library/ajax/ajax.file-upload.php';
var uploadDir = 'prodimages/';
$('.listing-image').fileupload({
url: url,
dataType: 'json',
autoUpload: true,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
maxFileSize: 1000000, // 1 MB
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
previewMaxWidth: 120,
previewMaxHeight: 120,
previewCrop: true,
paramName: 'files[]',
formData: {uploadDir: uploadDir}
})/*.on('fileuploadprocessalways', function (e, data) {
var index = data.index;
var file = data.files[index];
$(this).html(file.preview);
})*/.on('fileuploadprogressall', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('.listing-progress', this).css(
'width',
progress + '%'
);
}).on('fileuploaddone', function (e, data) {
var file = data.result.files[0];
var html = '<div class="listing-preview">\
<img src="' + file.thumbnailUrl + '" data-name="' + file.name + '">\
<div class="listing-preview-delete">Delete</div>\
</div>';
$(this).html(html).data('delete-url', file.deleteUrl).css('padding', 0);
Crop('/' + uploadDir + file.name, $(this).prop('id'));
});
});
This sends the crop details through to the PHP script above via Ajax.
$(document).on('click', '.crop-btn', function() {
var data = {
file: $(this).data('src'),
crop: jcrop.tellSelect()
}
ShowLoadingById('crop-loading');
AjaxHandler('/eshop/library/ajax/ajax.product-crop-thumb.php', data, 'POST', true);
});
The crop window is a lightbox, this function vertically centres it and resizes the image if it's vertically bigger than the available space.
function CentreCrop() {
var m = ($(window).height() - ($('.crop > div').height() + 60)) / 2;
$('.crop > div').css('margin-top', m);
if($('#crop-img').height() > $('.crop > div').height() - 30) {
$('#crop-img-container').height($('.crop > div').height() - 30);
}
}
This is the initial function that stores the file to be cropped and calls the worker if it's not running.
var toBeCropped = [];
var working = false;
function Crop(file, id) {
toBeCropped.push({path: file, id: id});
if( ! working) {
working = true;
CropWorker();
}
}
This is the function I run when the crop has finished to destroy jcrop and clear the crop lightbox ready for the next image to be cropped.
function CropSuccess() {
$('.crop').fadeOut(250, function() {
jcrop.destroy();
$(this).html('');
CropWorker();
});
}
This is the worker that actually creates the content in the lightbox and initiates jcrop.
function CropWorker() {
if(toBeCropped.length > 0) {
file = toBeCropped.shift();
html = '<div>\
<div id="crop-img-container" class="row-fluid">\
<img id="crop-img" src="' + file.path + '">\
</div>\
<div class="row-fluid">\
<div class="span3 offset9">\
<button class="span12 btn crop-btn" data-id="' + file.id + '" data-src="' + file.path + '">Create Thumb</button>\
</div>\
</div>\
<div class="row-fluid loading-screen" id="crop-loading">\
<div>\
<h4>Cropping...</h4>\
<img src="/img/loading.gif">\
</div>\
</div>\
</div>';
$('.crop').html(html);
$('.crop').fadeIn(250);
$('#crop-img').load(function() {
CentreCrop();
$('#crop-img').Jcrop({
aspectRatio: 1/1,
bgColor: 'black',
bgOpacity: 0.4,
boxWidth: $('#crop-img').width(), // Only just recently added boxWidth and height to see if that would fix it, no difference with or without.
boxHeight: $('#crop-img').height(),
//maxSize: [300,300],
minSize: [180,180]
}, function() {
jcrop = this;
jcrop.setSelect([0,0,180,180]);
});
});
} else {
working = false;
}
}
UPDATE
Part of the problem it seems was the image resizing. I was changing the image size to fit the screen, I though JCrop would take care of that for me but it seems you have to tell JCrop the original dimensions of the image. I've added the true size option when initializing JCrop and it seems we're almost there.
For smaller images, those under 1000px the cropper seems to be working great. But for larger ones (1000px +) it's producing black images. It doesn't do this when I use it with the JCrop demo script but the only difference between the two is one is outputting the file to the screen and the other is saving it. I can't see any other difference or figure out why this would happen.
UPDATE 2 It seems to only be affected if I'm running the code through Ajax. If I run the exact same function just posting to a page and running it at the top, the thumbnail is created perfectly every time, no matter what the size of the original image or the size of the box I draw.