Publi

Redimensionando fotos en PHP evitando que se deformen nuestras imágenes

photo-1464621922360-27f3bf0eca75
En las webs modernas se intenta que el usuario sea capaz de hacer casi de todo. Eso sí, siempre que de cara al servidor sea seguro. Por ejemplo, de nada vale que un usuario pueda generar a través de una web 1000000 de decimales del número PI si mientras tanto ningún usuario más es capaz de hacer nada.
Una de las tareas básicas de una web hoy e día es redimensionar un foto. Es decir, adaptar el tamaño de una foto para adecuarla a nuestras necesidades. Lo primero que tenemos que pensar es que a la hora de transmitir imágenes por la red, tenemos que evitar enviar archivos demasiado grandes. Por ejemplo, si tenemos que mostrar una imagen como máximo a 500×500, debemos evitar enviar la imagen a 4096×4096 ya que no vamos a mostrarla tan grande, vamos a desperdiciar ancho de banda nuestro enviando la foto y tiempo del usuario descargándola.
Todo eso sumado al auge de las webs dinámicas que deben permitir la subida de archivos de imagen al usuario (o al administrador) y de forma sencilla adaptarlas a la web. De forma que se pueda subir una foto directamente de la cámara digital y a la hora de visualizarla en la web quedarnos sólo con el tamaño o los tamaños que necesitemos.

Aunque, como siempre, tenemos varias formas de hacer las cosas, y, sobre todo, hay un factor que debemos tener en cuenta: el aspecto de la foto.

Aspecto de una imagen

El aspecto de una imagen no es más que la relación entre anchura y altura. Vamos, ancho partido por alto. Si os fijáis, es lo mismo que se utiliza en las pantallas. Ahora es común que las pantallas sean 16:9 (panorámicas, que son mucho más anchas que altas), antiguamente los monitores solían ser 4:3 (no eran cuadrados, pero casi). No obstante hay muchas relaciones de aspecto estándar para monitores, proyección, etc.
Cuando hablamos de imágenes, puede haber tantas relaciones de aspecto como imágenes, ya que las fotos podemos recortarlas de mil maneras, y tanto anchura como altura pueden tomar casi cualquier valor y su relación de aspecto se suele expresar como el resultado de la división entre ancho y algo. Si nuestra foto tiene una resolución de 320×240 su relación será 1,33 (¡ anda ! 4/3 da también 1,33 y es que esta imagen se podrá presentar a pantalla completa sin que se observe ninguna deformación), si nuestra foto tiene de dimensiones 600×500 la relación será de 1,2 y si la ponemos a pantalla completa en un monitor 4:3 la veremos deformada.

Dejo aquí la foto original con una resolución de 1280×792 y una relación de aspecto de 1,61:
baterias

Sobre PHP y Frameworks

Si estás trabajando con un framework o una biblioteca especializada en imágenes, seguramente dispongas de funciones o métodos para realizar estas operaciones de forma directa y con algunas opciones más. Pero si te gusta programar PHP a pelo, aquí encontrarás algunas guías que podrán ser útiles para tus proyectos. Aunque podemos utilizar otras bibliotecas para gráficos en PHP, como por ejemplo ImageMagick, estos ejemplos se han hecho utilizando GD.

Las funciones que utilizaré aquí serán sencillas, porque sobre todo el post va encaminado a la matemática de la transformación de las dimensiones y el recorte de la foto.

Redimensionado rápido y directo

Lo que hacemos aquí es cambiar el tamaño de la foto. Nos da igual la relación de aspecto, cogeremos una foto (baterias.jpg) y sean cuales sean sus dimensiones, las cambiaremos.
Para ello utilizaremos imagecopyresampled(), eso sí, si no tenemos cuidado con la resolución que pongamos, la imagen puede quedar deformada.
Si tomamos este código:

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
<?php

$imagefile = 'baterias.jpg';

/**
 * Opens new image
 *
 * @param $filename
 */

function icreate($filename)
{
  $isize = getimagesize($filename);
  if ($isize['mime']=='image/jpeg')
    return imagecreatefromjpeg($filename);
  elseif ($isize['mime']=='image/png')
    return imagecreatefrompng($filename);
  /* Add as many formats as you can */
}

/**
 * Simple image resample into new image
 *
 * @param $image Image resource
 * @param $width
 * @param $height
 */

function simpleresize($image, $width, $height)
{
  $new = imageCreateTrueColor($width, $height);
  imagecopyresampled($new, $image, 0, 0, 0, 0, $width, $height, imagesx($image), imagesy($image));
  return $new;
}

$imgh = icreate($imagefile);
$imgr = simpleresize($imgh, 520, 200);

header('Content-type: image/jpeg');
imagejpeg($imgr);

La imagen resultante será:
baterias_500_520_simple
Y, como no hemos respetado la relación de aspecto de la pantalla, la imagen sale deformada, las baterías salen como aplastadas. Esto nos puede servir para imágenes muy pequeñas, o para cuando sepamos (o no nos importe) que la imagen no va a sufrir una deformación.

Una dimensión fija

Si no queremos que la imagen sufra ninguna deformación, lo que podemos hacer es dejar una dimensión fija. Si dejamos el ancho fijo, con la relación de aspecto de la imagen original podemos calcular la nueva altura, y si dejamos la altura fija, podemos calcular la anchura. Siguiendo las siguientes ecuaciones:
ancho_alto
El código sería el siguiente:

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
<?php

$imagefile = 'baterias.jpg';

/**
 * Opens new image
 *
 * @param $filename
 */

function icreate($filename)
{
  $isize = getimagesize($filename);
  if ($isize['mime']=='image/jpeg')
    return imagecreatefromjpeg($filename);
  elseif ($isize['mime']=='image/png')
    return imagecreatefrompng($filename);
  /* Add as many formats as you can */
}

/**
 * Resize maintaining aspect ratio
 *
 * @param $image
 * @param $width
 */

function resizeAspectW($image, $width)
{
  $aspect = imagesx($image) / imagesy($image);
  $height = $width / $aspect;
  $new = imageCreateTrueColor($width, $height);

  imagecopyresampled($new, $image, 0, 0, 0, 0, $width, $height, imagesx($image), imagesy($image));
  return $new;
}

/**
 * Resize maintaining aspect ratio
 *
 * @param $image
 * @param $height
 */

function resizeAspectH($image, $height)
{
  $aspect = imagesx($image) / imagesy($image);
  $width = $height * $aspect;
  $new = imageCreateTrueColor($width, $height);

  imagecopyresampled($new, $image, 0, 0, 0, 0, $width, $height, imagesx($image), imagesy($image));
  return $new;
}

$imgh = icreate($imagefile);
$imgr = resizeAspectH($imgh, 520);

header('Content-type: image/jpeg');
imagejpeg($imgr);

Donde podemos cambiar la llamada a resizeAspectW() por resizeAspectH() dándonos como resultado las siguientes imágenes:

baterias_resized_w
520×321
baterias_resized_h
840×520

Mantener aspecto sin pasarse de dimensiones

El problema con el algoritmo anterior es que las dimensiones pueden crecer mucho. Sobre todo si permitimos que el usuario nos suba fotografías, pueden subir una foto alargada de dimensiones 1×10000 (aspecto 0,0001) si queremos fijar el ancho a 200 la altura saldría a 2000000 lo que es inviable. Por tanto, debemos controlar cuánto crece la imagen. Podemos seguir este código que fijará las dimensiones máximas a las que vamos a tener la imagen resultante. La relación de aspecto se mantendrá y no se superarán las dimensiones fijadas:

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
<?php

$imagefile = 'baterias.jpg';

/**
 * Opens new image
 *
 * @param $filename
 */

function icreate($filename)
{
  $isize = getimagesize($filename);
  if ($isize['mime']=='image/jpeg')
    return imagecreatefromjpeg($filename);
  elseif ($isize['mime']=='image/png')
    return imagecreatefrompng($filename);
  /* Add as many formats as you can */
}

/**
 * Resize image maintaining aspect ratio, occuping
 * as much as possible with width and height inside
 * params.
 *
 * @param $image
 * @param $width
 * @param $height
 */

function resizeMax($image, $width, $height)
{
  /* Original dimensions */
  $origw = imagesx($image);
  $origh = imagesy($image);

  $ratiow = $width / $origw;
  $ratioh = $height / $origh;
  $ratio = min($ratioh, $ratiow);

  $neww = $origw * $ratio;
  $newh = $origh * $ratio;

  $new = imageCreateTrueColor($neww, $newh);

  imagecopyresampled($new, $image, 0, 0, 0, 0, $neww, $newh, $origw, $origh);
  return $new;
}

$imgh = icreate($imagefile);
$imgr = resizeMax($imgh, 400, 200);

header('Content-type: image/jpeg');
imagejpeg($imgr);

La imagen resultante ahora será (323×200):
baterias_resized_max
Os invito a probar este algoritmo con otras dimensiones de imagen para ver cómo se comporta.

Redimensiona y recorta (resizeCrop)

En el caso anterior, una dimensión sí que quedará fija (se elegirá en función de la relación de aspecto), pero, si queremos que las dos dimensiones queden fijas sin que se pierda la relación de aspecto, lo que debemos hacer es recortar la imagen. De esta forma perderemos información, pero la foto se seguirá viendo bien, aunque deberíamos decidir de dónde perdemos información, ya que si la imagen resultante es más alta que la deseada, debemos perder información vertical y si es más ancha horizontal.
Podemos seguir el siguiente algoritmo:

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
<?php

$imagefile = 'baterias.jpg';

/**
 * Opens new image
 *
 * @param $filename
 */

function icreate($filename)
{
  $isize = getimagesize($filename);
  if ($isize['mime']=='image/jpeg')
    return imagecreatefromjpeg($filename);
  elseif ($isize['mime']=='image/png')
    return imagecreatefrompng($filename);
  /* Add as many formats as you can */
}

function resizeCrop($image, $width, $height, $displ='center')
{
  /* Original dimensions */
  $origw = imagesx($image);
  $origh = imagesy($image);

  $ratiow = $width / $origw;
  $ratioh = $height / $origh;
  $ratio = max($ratioh, $ratiow); /* This time we want the bigger image */

  $neww = $origw * $ratio;
  $newh = $origh * $ratio;

  $cropw = $neww-$width;
  /* if ($cropw) */
  /*   $cropw/=2; */
  $croph = $newh-$height;
  /* if ($croph) */
  /*   $croph/=2; */

  if ($displ=='center')
    $displ=0.5;
  elseif ($displ=='min')
    $displ=0;
  elseif ($displ=='max')
    $displ=1;

  $new = imageCreateTrueColor($width, $height);

  imagecopyresampled($new, $image, -$cropw*$displ, -$croph*$displ, 0, 0, $width+$cropw, $height+$croph, $origw, $origh);
  return $new;
}

$imgh = icreate($imagefile);
$imgr = resizeCrop($imgh, 520, 500, 0.4);

header('Content-type: image/jpeg');
imagejpeg($imgr);

La imagen resultante será (520×500):
baterias_resized_crop
El cuarto argumento, $displ podrá tener uno de los siguientes valores:

  • ‘min’: recortará la imagen arriba o a la izquierda
  • ‘center’: se quedará con la parte central de la imagen
  • ‘max’: recortará la imagen abajo o a la derecha
  • 0>=x>=1 (cualquier valor entre 0 y 1): desplazará la ventana de recorte hacia la derecha o abajo. Podemos interpretarlo como un porcentaje.

Rotar y recortar

Si queremos aplicar un efecto chulo a nuestras fotos, podemos rotarlas unos grados. El problema es que se crearán unos bordes negros (también podemos hacerlos transparentes), pero nos incomodarán. Así que, vamos a proceder a buscar la región cuadrada más grande de imagen sin bordes y recortar la imagen con esas dimensiones en esa posición (primero redimensiono la imagen para hacerla más cómoda para trabajar):

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
<?php

$imagefile = 'baterias.jpg';

/**
 * Opens new image
 *
 * @param $filename
 */

function icreate($filename)
{
  $isize = getimagesize($filename);
  if ($isize['mime']=='image/jpeg')
    return imagecreatefromjpeg($filename);
  elseif ($isize['mime']=='image/png')
    return imagecreatefrompng($filename);
  /* Add as many formats as you can */
}

/**
 * Resize image maintaining aspect ratio, occuping
 * as much as possible with width and height inside
 * params.
 *
 * @param $image
 * @param $width
 * @param $height
 */

function resizeMax($image, $width, $height)
{
  /* Original dimensions */
  $origw = imagesx($image);
  $origh = imagesy($image);

  $ratiow = $width / $origw;
  $ratioh = $height / $origh;
  $ratio = min($ratioh, $ratiow);

  $neww = $origw * $ratio;
  $newh = $origh * $ratio;

  $new = imageCreateTrueColor($neww, $newh);

  imagecopyresampled($new, $image, 0, 0, 0, 0, $neww, $newh, $origw, $origh);
  return $new;
}

function rotateCrop($image, $deg)
{
  $width = imagesx($image);
  $height = imagesy($image);

  $long = max($width, $height);
  $short = min($width, $height);

  $radians = deg2rad($deg);
  $_sin = abs(sin($radians));
  $_cos = abs(cos($radians));

  if ($short <= 2*$_sin*$_cos*$long)
    {
      $x = 0.5*$short;
      if ($width>$height)
    {
      $neww = $x/$_sin;
      $newh = $x/$_cos;
    }
      else
    {
      $neww = $x/$_cos;
      $newh = $x/$sin;
    }
    }
  else
    {
      $_cos2 = $_cos*$_cos - $_sin*$_sin;
      $neww = ($width*$_cos - $height*$_sin)/$_cos2;
      $newh = ($height*$_cos - $width*$_sin)/$_cos2;
    }

  $rot = imagerotate($image, $deg, 0);
  $new = imageCreateTrueColor($neww, $newh);

  imagecopy($new, $rot, 0,0,  imagesx($rot)/2-$neww/2 , imagesy($rot)/2-$newh/2 ,$neww, $newh);
  return $new;

}

$imgh = icreate($imagefile);
$imgr = resizeMax($imgh, 520, 200);
$imgv = rotateCrop($imgr, 30);
header('Content-type: image/jpeg');
imagejpeg($imgv);

El resultado sería algo como esto:
baterias_resized_rotcrop

Foto principal: Tony Webster

También podría interesarte....

Leave a Reply