Entrada

recortar imagenes en cakephp con jquery.

Para poder recortar una imagen mediante una seleccion se consigue con imgAreaSelect que es un plugin de JQuery para seleccionar un area de una imagen.

El publin se puede configurar y permite diferentes funcionalidades que nos ayudará y nos enriquecerá la tarea de recortar una imagen, ya sea a nosotros a los usuarios cuando por ejemplo quieren subir un avatar y recortar la zona que quieran de la foto previamente subida.

El plugin permite trabajar escalando imágenes o recortando unas dimesiones asignadas. Por ejemplo. 1.- Selecciona y recorta una imagen con unas dimensiones determinadas.

1
$(document).ready(function () { $('#avatar').imgAreaSelect({ maxWidth: 200, maxHeight: 150, handles: true }); });

2.- Selecciona y recorta la foto manteniendo un porcentaje escalado previamente asignado.

1
$(document).ready(function () { $('#avatar').imgAreaSelect({ aspectRatio: '4:3', handles: true }); });

Esto es muy sencillo de integrar en php y jquery pero bueno como ya sabemos en Cakephp se complica un poquito mas debiado a que hay que seguir el patrón de diseño MVC. Como siempre habrá que crear un componente que interactue con el controlador o controladores, un helper para incluir en la vista las funciones de javascript y la implementación de la vista.

Para esta receta necesitaremos ;)

1.- El componente /app/controllers/components/jq_imgcrop.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?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";
        $max_width = 400;
        // añado al prefijo, el identificador de tiempo, para que el archivo que se suba siempre sea unico
        $prefix = $prefix.time();

        $userfile_name = $uploadedInfo['name'];
        $userfile_tmp =  $uploadedInfo["tmp_name"];
        $userfile_size = $uploadedInfo["size"];
        //$filename = $prefix.basename($uploadedInfo["name"]);
        $id_unic = $uuid = String::uuid();
        $extension =  $this->getFileExtension($uploadedInfo["name"]);
        if (($extension != "jpeg")  && ($extension != "jpg") && ($extension != "gif") && ($extension != "png"))
        {
            return false;
        }
        $filename = $prefix.$id_unic.'.'.$extension;
        //$file_ext = mb_substr($filename, strrpos($filename, ".") + 1);
        $uploadTarget = $upload_path.$filename;

        if(empty($uploadedInfo)) {
                  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(mb_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($image, 0777);
        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(mb_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_name, 0777);
        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;
    }

     function getFileExtension($str)
     {
        /*
        $i = strrpos($str,".");
        if (!$i) { return ""; }
        $l = strlen($str) - $i;
        $ext = mb_substr($str,$i,$l);
        return $ext;
        */
        return end(explode(".", $str));

    }
}
?>

2.- El Helper

/app/views/helpers/cropimage.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?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;

                  /*
                  Esta parte esta dando un bug en IE x aunque en el resto de browsers va ok.
                    $('#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('Seleccione sobre la foto la zona que quiere');
                            return false;
                        }else{
                            return true;
                    }
                });
            });

            $(window).load(function () {
                $('#thumbnail').imgAreaSelect({ aspectRatio:'4:3', 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; display:none">
                    <img src="$imagePath" style="position: relative;" alt="Thumbnail Preview" />
                </div>
                <br style="clear:both;"/>$x1 $y1 $x2 $y2 $w $h $imgP");
    }
}
?>

3.- No olvidar incluir el componente ni el helper en el controlador

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

4.- Voy a integrar el ejemplo de redimensionar una imagen o una foto en un ejemplo real. En este caso yo he hecho una funcion para crear o modifcar el avatar del usuario en el controlador /app/controllers/usuarios_controller.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
   <?php
    /**
    * funcion para crear/ modificar el avatar del usuario
    * @version 1.0
    * @author Pedro Ventura
    *
    */
    function alterar_avatar()
    {
        $path_thmb_subida = $this->Avatars->comprobar_ruta_subida('usuarios');
        if($path_thmb_subida)
        {
            if(!empty($this->data))
            {
                if(isset($this->data['Avatar']['step']) && ($this->data['Avatar']['step']=='1'))
                {
                    # subo esta que será la original
                    # esta que subo despues se pisará con el thumb del cropping
                    $uploaded = $this->JqImgcrop->uploadImage($this->data['Avatar']['name1'], $path_thmb_subida, 'original_');
                    if($uploaded)
                    {
                        $this->set('uploaded',$uploaded);
                        $this->set('step',2);
                    }
                    else
                    {
                        $this->Session->setFlash(__('Tipo de archivo incorrecto, seleciona una imagen .jpg, .jpeg, .gif o .png',true));
                        $this->set('step',1);
                    }
                }
                if(isset($this->data['Avatar']['step']) && ($this->data['Avatar']['step']=='2'))
                {
                    # sustituyo el path del thumbnail que se acaba de subir pues es el original y cambio el nombre del archivo,
                    # para que se genere un thumbnail con un nombre nuevo
                    $path_thmb =  str_replace('original_','thumb_',$this->data['Usuario']['imagePath']);
                    $cropped = $this->JqImgcrop->cropImage(85, $this->data['Usuario']['x1'], $this->data['Usuario']['y1'], $this->data['Usuario']['x2'], $this->data['Usuario']['y2'], $this->data['Usuario']['w'], $this->data['Usuario']['h'], $path_thmb, $this->data['Usuario']['imagePath']);
                    $this->set('step',3);
                    # guardo el path del avatar en el registro del usuario
                    $this->Usuario->id = $_SESSION['Usuario']['id'];
                    $this->Usuario->saveField('avatar_path',$path_thmb);
                    $this->Session->setFlash(__('Avatar Alterado con éxito',true));
                    # seteo el avatar resultante despues de la redimension y lo asigno a la vista
                    $this->set('avatar_final',str_replace(WWW_ROOT,'',$cropped));
                }
            }
            else
            {
                $this->set('step',1);
            }
        }
    }
?>

5.- Los pasos para recortar la imagen integrados en la vista son los siguientes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?
    /* librerías para crop de imagenes */
    echo $html->css('img_select/imgareaselect-animated_0.9.2',NULL, false). " n";
    echo $javascript->link('jquery.imgareaselect_0.9.2.js',NULL, false);
    # formulario paso 1 de crear avatar
    if(isset($step) && ($step == 1))
    {
        echo $form->create('Usuario', array('type' => 'file','url' => array('controller' => 'usuarios', 'action' => 'alterar_avatar')));
        echo '<br />';
        echo $form->input('Avatar.step', array('label' => false,'div'=>null, 'type' => 'hidden', 'value'=>$step));
        echo '<br />';
        echo 'Selecciona tu avatar : '.  $form->file('Avatar.name1', array('size' => '60'));
        echo $form->submit('Guarda Avatar');
    }
    elseif(isset($step) && ($step == 2))
    {
        echo $cropimage->createJavaScript($uploaded['imageWidth'],$uploaded['imageHeight'],85,85,$uploaded["imagePath"]);
        echo $form->create('Usuario', array('type' => 'file','url' => array('controller' => 'usuarios', 'action' => 'alterar_avatar')));
        echo $form->input('Avatar.step', array('label' => false,'div'=>null, 'type' => 'hidden', 'value'=>$step));
        echo $cropimage->createForm($uploaded["imagePath"], 85, 85);
        echo $form->submit('Terminar', array("id"=>"save_thumb"));
        echo $form->end();
    }
    elseif(isset($step) && ($step == 3))
    {
        ?>
        <br />
        <p>Enhorabuena!! ya has creado tu avatar!! lo podrás ver en tu perfil.</p>
        <br />
        <?
        echo $html->image($avatar_final);
    }
    else
    {
        # sin acciones
    }
?>

Para los que les cueste seguir el flujo de datos, resumo a de manera básica.

  1. Se incluyen el helper y el componente en el controlador
  2. Una vez que se hace la llamada a la funcion empieza en step =1, es decir que dentro de la vista se va a mostrar la renderización que hay dentro de step 1. Esto se hace porque dentro de la funcionalidad de recortar hay 3 fases:
    • La fase inicial donde el usuario selecciona la foto que quiere subir y se envía. El controlador la recoge y la envía al componente que la sube.

    • Si el paso anterior se ha realizado con éxito, se para al step 2 que es cuando se tiene que seleccionar una zona de la foto mediante el propio plugin de Jquery ImgAreaSelect. Se recorgan y en en formulario se generan unas variables en input hidden con los valores del recorte realizado. Se envia al controlador y el contralador se lo delega al componente que realiza la redimension o recorte

    • Finalmente ya tenemos la imagen final recortada dependiendo de los patrones de redimension que hayamos configurado

En el helper hay un comentario, ya que hay un bug que no consigo arreglar, por falta de tiempo o simplemente porque estoy ciego y no veo el error. Hay una función preview que muestra una previsualizacion de como quedará la imagen o la foto después de ser recortada, yo la tengo comentada por esto mismo que comento. Está fallando en cualquier version de IE, intuyo que será por un tema de comillas, que no las interpreta bien y no es capaz de cerrarlas. Si alguien consigue debugear esta parte, por favor que lo comente!!

1
2
3
4
5
6
7
8
9
/*
  Esta parte esta dando un bug en IE x aunque en el resto de browsers va ok.
    $('#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'
    });
    */

Cualquier duda o pregunta comentarlas y las iré resolviendo.

Actualización: 25/11/201 Se me ha olvidado por completo indicar que hay que descargar la libreria de jquery, así como el plugin de imageArea. Los adjunto en el siguiente zip. También contiene un css y algunas imágenes para hacer todo el efecto de recorte.
[download id="8" format="4" autop="false"]
Esta entrada está licenciada bajo CC BY 4.0 por el autor.