jQuery image upload & crop

By Thomas (klagoggle)
I was searching for a nice script with that i can upload and crop images.
I found a nice one @ http://www.webmotionuk.co.uk/php-jquery-image-upload-and-crop/ and made it cake ready ;)
So, i hope i can help you with my first Article!

Here we go:

Requirements


Installation


Ok, now we must create a new Component called "JqImgcrop".
Let`s create a new file called "jq_imgcrop.php" in yourapp/controllers/components/ with following content:

Component Class:

Download code <?php 
class JqImgcropComponent extends Object {

    function 
uploadImage($uploadedInfo$uploadTo$prefix){
        
$webpath $uploadTo;
        
$upload_dir WWW_ROOT.str_replace("/"DS$uploadTo);
        
$upload_path $upload_dir.DS;
        
$max_file "34457280";                         // Approx 30MB
        
$max_width 800;

        
$userfile_name $uploadedInfo['name'];
        
$userfile_tmp =  $uploadedInfo["tmp_name"];
        
$userfile_size $uploadedInfo["size"];
        
$filename $prefix.basename($uploadedInfo["name"]);
        
$file_ext substr($filenamestrrpos($filename".") + 1);
        
$uploadTarget $upload_path.$filename;

        if((!empty(
$uploadedInfo)) && ($uploadedInfo["error"] == 0)) {
            return 
false;
        }

        if (isset(
$uploadedInfo['name'])){
            
move_uploaded_file($userfile_tmp$uploadTarget );
            
chmod ($uploadTarget 0777);
            
$width $this->getWidth($uploadTarget);
            
$height $this->getHeight($uploadTarget);
            
// Scale the image if it is greater than the width set above
            
if ($width $max_width){
                
$scale $max_width/$width;
                
$uploaded $this->resizeImage($uploadTarget,$width,$height,$scale);
            }else{
                
$scale 1;
                
$uploaded $this->resizeImage($uploadTarget,$width,$height,$scale);
            }
        }
        return array(
'imagePath' => $webpath.$filename'imageName' => $filename'imageWidth' => $this->getWidth($uploadTarget), 'imageHeight' => $this->getHeight($uploadTarget));
    }

    function 
getHeight($image) {
        
$sizes getimagesize($image);
        
$height $sizes[1];
        return 
$height;
    }
    function 
getWidth($image) {
        
$sizes getimagesize($image);
        
$width $sizes[0];
        return 
$width;
    }

    function 
resizeImage($image,$width,$height,$scale) {
        
$newImageWidth ceil($width $scale);
        
$newImageHeight ceil($height $scale);
        
$newImage imagecreatetruecolor($newImageWidth,$newImageHeight);
$ext strtolower(substr(basename($image), strrpos(basename($image), ".") + 1));
        
$source "";
        if(
$ext == "png"){
            
$source imagecreatefrompng($image);
        }elseif(
$ext == "jpg" || $ext == "jpeg"){
            
$source imagecreatefromjpeg($image);
        }elseif(
$ext == "gif"){
            
$source imagecreatefromgif($image);
        }
        
imagecopyresampled($newImage,$source,0,0,0,0,$newImageWidth,$newImageHeight,$width,$height);
        
imagejpeg($newImage,$image,90);
        
chmod($image0777);
        return 
$image;
    }

    function 
resizeThumbnailImage($thumb_image_name$image$width$height$start_width$start_height$scale){
        
$newImageWidth ceil($width $scale);
        
$newImageHeight ceil($height $scale);
        
$newImage imagecreatetruecolor($newImageWidth,$newImageHeight);
        
$ext strtolower(substr(basename($image), strrpos(basename($image), ".") + 1));
        
$source "";
        if(
$ext == "png"){
            
$source imagecreatefrompng($image);
        }elseif(
$ext == "jpg" || $ext == "jpeg"){
            
$source imagecreatefromjpeg($image);
        }elseif(
$ext == "gif"){
            
$source imagecreatefromgif($image);
        }
        
imagecopyresampled($newImage,$source,0,0,$start_width,$start_height,$newImageWidth,$newImageHeight,$width,$height);
        
imagejpeg($newImage,$thumb_image_name,90);
        
chmod($thumb_image_name0777);
        return 
$thumb_image_name;
    }

    function 
cropImage($thumb_width$x1$y1$x2$y2$w$h$thumbLocation$imageLocation){
        
$scale $thumb_width/$w;
        
$cropped $this->resizeThumbnailImage(WWW_ROOT.str_replace("/"DS,$thumbLocation),WWW_ROOT.str_replace("/"DS,$imageLocation),$w,$h,$x1,$y1,$scale);
        return 
$cropped;
    }
}
?>


Then we must create a new helper called cropimage.php (you must create this file in yourapp/views/helpers/ )

Helper Class:

Download code <?php 
class CropimageHelper extends Helper {
    var 
$helpers = array('Html''Javascript''Form');

    function 
createJavaScript($imgW$imgH$thumbW$thumbH) {
            return 
$this->output("<script type=\"text/javascript\">
                function preview(img, selection) {
                    var scaleX = $thumbW / selection.width;
                    var scaleY = $thumbW / selection.height;

                    $('#thumbnail + div > img').css({
                        width: Math.round(scaleX * $imgW) + 'px',
                        height: Math.round(scaleY * $imgH) + 'px',
                        marginLeft: '-' + Math.round(scaleX * selection.x1) + 'px',
                        marginTop: '-' + Math.round(scaleY * selection.y1) + 'px'
                    });
                    $('#x1').val(selection.x1);
                    $('#y1').val(selection.y1);
                    $('#x2').val(selection.x2);
                    $('#y2').val(selection.y2);
                    $('#w').val(selection.width);
                    $('#h').val(selection.height);
                }

                $(document).ready(function () {
                    $('#save_thumb').click(function() {
                        var x1 = $('#x1').val();
                        var y1 = $('#y1').val();
                        var x2 = $('#x2').val();
                        var y2 = $('#y2').val();
                        var w = $('#w').val();
                        var h = $('#h').val();
                        if(x1==\"\" || y1==\"\" || x2==\"\" || y2==\"\"|| w==\"\" || h==\"\"){
                            alert('Please choose a area to crop...');
                            return false;
                        }else{
                            return true;
                    }
                });
            });

            $(window).load(function () {
                $('#thumbnail').imgAreaSelect({ aspectRatio: '1:1', onSelectChange: preview });
            });
            </script>"
);
    }

    function 
createForm($imagePath$tH$tW){
        
$x1 =         $this->Form->hidden('x1', array("value" => """id"=>"x1"));
            
$y1 =         $this->Form->hidden('y1', array("value" => """id"=>"y1"));
            
$x2 =         $this->Form->hidden('x2', array("value" => """id"=>"x2",));
            
$y2 =         $this->Form->hidden('y2', array("value" => """id"=>"y2"));
            
$w =             $this->Form->hidden('w', array("value" => """id"=>"w"));
            
$h =             $this->Form->hidden('h', array("value" => """id"=>"h"));
            
$imgP =      $this->Form->hidden('imagePath', array("value" => $imagePath));
            return 
$this->output("<img src=\"$imagePath\" style=\"float: left; margin-right: 10px;\" id=\"thumbnail\" alt=\"Create Thumbnail\" />
            <div style=\"position:relative; overflow:hidden; width:"
.$tW."px; height:".$tH."px;\"> 
                <img src=\"$imagePath\" style=\"position: relative;\" alt=\"Thumbnail Preview\" />
            </div>
            <br style=\"clear:both;\"/>$x1 $y1 $x2 $y2 $w $h $imgP"
);
    }
}
?>


Thats it, now i will tell you something about the

Usage


Ok, now you must add the component and the helper to your controller where you want to use the cropload ( :) )

Download code
    var $helpers = array(..., 'Cropimage')
    var $components = array(..., 'JqImgcrop');

Now you have to create a form where you can select a image.
I will give you an example:

Download code
<?php echo $form->create('YourModel', array('action' => 'createimage_step2'"enctype" => "multipart/form-data"));?>
    <?php
        
echo $form->input('name');
        echo 
$form->input('image',array("type" => "file")); 
        echo 
$form->end('Upload');
    
?>


Now, in the createimage_step2 function you have to add the upload function of our component:

Download code
$uploaded = $this->JqImgcrop->uploadImage($this->data['YourModel']['image'], '/img/upload/', 'prefix_');

and then push it to the view:
Download code
$this->set('uploaded',$uploaded);

This is what it looks in my controller:

Download code
    function createimage_step2(){
        if (!empty($this->data)) {
            $uploaded = $this->JqImgcrop->uploadImage($this->data['YourModel']['image'], '/img/upload/', 'prefix_');
            $this->set('uploaded',$uploaded);
        }
    

Now you have to add in your view ( in my case createimage_step2.ctp ) at the top the 2 Javascript libraries:

Download code
<?php
if(isset($javascript)):
        echo 
$javascript->link('jquery-1.2.6.min.js');
        echo 
$javascript->link('jquery.imgareaselect-0.4.2.min.js');
endif;
?>

Then you have to create a new form with the cropimage helper using:
Download code
echo $cropimage->createForm($uploaded["imagePath"], $width, $height);
The 2 width and height parameters specify the size of the thumbnail which will be created.

You have to close the form with a form->submit with the id "save_thumb" Download code
echo $form->submit('Done', array("id"=>"save_thumb"));

In my case it looks like this:

Download code
<?php 
        
echo $form->create('YourModel', array('action' => 'createimage_step3',"enctype" => "multipart/form-data"));    
        echo 
$form->input('id');
        echo 
$form->hidden('name');
        echo 
$cropimage->createForm($uploaded["imagePath"], 151151);
        echo 
$form->submit('Done', array("id"=>"save_thumb"));
echo 
$form->end();?>

Final Step

Now, in the createimage_step3 function in your controller we crop and save the image

Download code
$this->JqImgcrop->cropImage(151, $this->data['YourModel']['x1'], $this->data['YourModel']['y1'], $this->data['YourModel']['x2'], $this->data['YourModel']['y2'], $this->data['YourModel']['w'], $this->data['YourModel']['h'], $this->data['YourModel']['imagePath'], $this->data['YourModel']['imagePath'])

The function is using this pararmeters:
Download code
$thumb_width, $x1, $y1, $x2, $y2, $w, $h, $thumbLocation, $imageLocation
$thumb_width is the width of your thumbnail, $x1, $y1, $x2, $y2, $w and $h are including the crop parameters, $thumbLocation the location where you want to save your thumb, and $imageLocation the location of the sourceimage.

This function returns the filename of the created thumbnail.

Ok, that's it.

Big thanks to http://www.webmotionuk.co.uk for your great article!

 

Comments 693

CakePHP Team Comments Author Comments
 

Comment

1 Small corrections and adjustments

Thank you for the article.
I tryed it and managed to get it work for windows, running Cake 1.2 RC1 on XAMPP PHP 5.2.0

Corrections:

Under Usage:

  echo $form->input('image',"type" => "file");  
should be

  echo $form->input('image',array("type" => "file"));

To make it work for windows I needed to change the component


        if((!empty($uploadedInfo)) && ($uploadedInfo["error"] == 0)) {
            return false;
        } 

into


        if(empty($uploadedInfo)) {
            return false;
        } 

or remove it, as Error allways showed up 0, even on sucess.

Further I removed the two


 chmod ($uploadTarget , 0777); 
and
chmod($image, 0777);

lines.

My createimage_step2.ctp view needed


echo $cropimage->createJavaScript($uploaded['imageWidth'],$uploaded['imageHeight'],151,151);

to make the script run.

And as I am not running only this app, the image path showed up wrong. So I rewrote the createForm() in the cropimage.php helper, using the html helper to create the images with the correct pats.


 function createForm($imagePath, $tH, $tW){
        $x1 =         $this->Form->hidden('x1', array("value" => "", "id"=>"x1"));
            $y1 =         $this->Form->hidden('y1', array("value" => "", "id"=>"y1"));
            $x2 =         $this->Form->hidden('x2', array("value" => "", "id"=>"x2",));
            $y2 =         $this->Form->hidden('y2', array("value" => "", "id"=>"y2"));
            $w =             $this->Form->hidden('w', array("value" => "", "id"=>"w"));
            $h =             $this->Form->hidden('h', array("value" => "", "id"=>"h"));
            $imgP =      $this->Form->hidden('imagePath', array("value" => $imagePath));
            $imgTum = $this->Html->image($imagePath, array('style'=>'float: left; margin-right: 10px;', 'id'=>'thumbnail', 'alt'=>'Create Thumbnail'));
            $imgTumPrev = $this->Html->image($imagePath, array('style'=>'position: relative;', 'id'=>'thumbnail', 'alt'=>'Thumbnail Preview'));
            return $this->output("$imgTum
            <div style=\"position:relative; overflow:hidden; width:151px; height:151px;\">
                $imgTumPrev
            </div>
            <br style=\"clear:both;\"/>$x1 $y1 $x2 $y2 $w $h $imgP");
    }
Posted Jun 12, 2008 by Eskil Mjelva Saatvedt
 

Comment

2 really nice thing but not quite working

sure looks beautiful in the examples!
i even got it to work - partly - and only with the correctios of Eskil

the main problem is this crap about these DS-Seperators
once it works in createimage_part2, but then theres one Slash to much for _part3, this is on localhost (windows xp)
then i upload it on my webspace, now the slahes are the other way arround and still everything is just really messy and nothing actually works.

what can i do about it?
isn't there a nice function or something to be able to work with http paths like "http://localhost/cake/img/upload/pic.jpg"?
or even better with relative ones like "/cake/img/upload/pic.jpg"?
i used to do that with my upload scripts once..
this worked on localhost as well as in the www

in this case this whole thing about this paths "F:\xampp\htdocs\cake\app\webroot\/img/temp_pic.jpg" etc would't even occur.. or?

i mean, this relative and http paths always use this one forward slash: /

well, i tried about all kinds of things, i'm so tired of it.
i hope somebody can help

i already startet to put some constants in my bootstrap.php to get this thing fixed:

define('HTTP_HOST',$_SERVER['HTTP_HOST']);        // e.g. localhost
define('HTTP_BASE', 'http://'.HTTP_HOST.'/');     // e.g.    http://localhost/
define ('PATH_REL', '/home/cake/');    // e.g. /home/cake/
define ('PATH_HOME', 'http://'.HTTP_HOST.PATH_REL);      // e.g. http://localhost/home/cake/

but this can't be the intention of our Cake-Programmers to workaround it, can it?
Posted Jul 3, 2008 by Mark
 

Comment

3 Hmm

Will test it on my windows system and will try to submit a solution as soon as possible.

Thanks to Eskil and Mark.
Posted Jul 16, 2008 by Thomas
 

Comment

4 Very cool script

Hi,
just tested after some tweaking (thank you Eskil) it works on my Mac.

Note that I modified the cropImage return's to:
//return $cropped;
return $imageLocation;

In this way you can display directly the cropped image in the final page:
<?php
echo $html->image($uploaded);
?>

Thanks a lot!
Posted Jul 27, 2008 by Germain Guglielmetti
 

Comment

5 Problem with session !

Well...

I got a social network, and the users can create projects... on creation, users can crop an image that represent the respective project so... heres my problem when I load the UserProjectsController it loads:

var $helpers = array('Cropimage');
var $components = array('JqImgcrop');

The two components loads but my app isn't able to read the Session anymore...

I need the user_id that is in the session to be able to copy the uploaded image in the correct folder on the server(localhost for now)...

Anyone did get the same problem ?!?

thks a lot for any help
Posted Oct 23, 2008 by Sébastien Galarneau
 

Bug

6 fix for the thumbnail preview div

FYI... In the helper class, you have change:

<div style=\"position:relative; overflow:hidden; width:151px; height:151px;\">
to

<div style=\"position:relative; overflow:hidden; width:".$tW."px; height:".$tH."px;\">
if you want your thumbnail height/width honored in the preview div.
Posted Mar 8, 2009 by Andrew Koransky
 

Comment

7 thanks

thanks for your suggestion.

i edited it.
Posted Mar 8, 2009 by Thomas
 

Question

8 hi i got a problem...

I'm now at cake
and tried to bake the the image uploader on ubuntu
the thing is i pass throgh the first step when i get to step 2 the script dos show me the image I upload to corp.
but know it dosent corp.
Some one has a clue...?

Posted Mar 16, 2009 by avi
 

Comment

9 I think you missed something..

Did you put these code o your _step2 view?
echo $cropimage->createJavaScript($uploaded['imageWidth'],$uploaded['imageHeight'],151,151);
I'm now at cake
and tried to bake the the image uploader on ubuntu
the thing is i pass throgh the first step when i get to step 2 the script dos show me the image I upload to corp.
but know it dosent corp.
Some one has a clue...?

Posted May 13, 2009 by Sebastian
 

Comment

10 Bug in IE*?

I have implemented this on a site i am building currently which includes an image gallery with user submissions.

Everything was working fine in Firefox, Google Chrome etal. But I was having problems with IE (of all flavours). No selection tool would be presented and a javascript warning was produced which told me about an 'Invalid argument' being submitted to the Math.round function.

The cause was that when you first click on the image to start your selection, the scaleX and scaleY variables in the javascript on the page result in a value of Infinity. Firefox and every other browser seems to silently step over this and carry on processing as normal. IE of course did not.

The solution was to add the following line after the initial scaleX and scaleY variables are calculated. This appears to have solved the problem fully.
if(scaleX == Infinity || scaleY == Infinity) return false;
I hope this helps someone else and saves them the hour of hunting it cost me ;o)

other than that, awesome plugin and it works really well.
Posted May 15, 2009 by Matt Keeble