¿Cuál es la forma más eficiente de cambiar el tamaño de imágenes grandes en PHP?
Actualmente estoy usando la función GD imagecopyresampled para tomar imágenes de alta resolución y redimensionarlas limpiamente a un tamaño para visualización web (aproximadamente 700 píxeles de ancho por 700 píxeles de alto).
Esto funciona muy bien en fotos pequeñas (menos de 2 MB) y toda la operación de cambio de tamaño tarda menos de un segundo en el servidor. Sin embargo, el sitio eventualmente brindará servicios a los fotógrafos que pueden estar cargando imágenes de hasta 10 MB de tamaño (o imágenes de hasta 5000x4000 píxeles de tamaño).
Hacer este tipo de operación de cambio de tamaño con imágenes grandes tiende a aumentar el uso de la memoria por un margen muy grande (las imágenes más grandes pueden aumentar el uso de la memoria para el script por encima de 80 MB). ¿Hay alguna forma de hacer que esta operación de cambio de tamaño sea más eficiente? ¿Debería usar una biblioteca de imágenes alternativa como ImageMagick ?
En este momento, el código de cambio de tamaño se parece a esto
function makeThumbnail($sourcefile, $endfile, $thumbwidth, $thumbheight, $quality) {
// Takes the sourcefile (path/to/image.jpg) and makes a thumbnail from it
// and places it at endfile (path/to/thumb.jpg).
// Load image and get image size.
$img = imagecreatefromjpeg($sourcefile);
$width = imagesx( $img );
$height = imagesy( $img );
if ($width > $height) {
$newwidth = $thumbwidth;
$divisor = $width / $thumbwidth;
$newheight = floor( $height / $divisor);
} else {
$newheight = $thumbheight;
$divisor = $height / $thumbheight;
$newwidth = floor( $width / $divisor );
}
// Create a new temporary image.
$tmpimg = imagecreatetruecolor( $newwidth, $newheight );
// Copy and resize old image into new image.
imagecopyresampled( $tmpimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height );
// Save thumbnail into a file.
imagejpeg( $tmpimg, $endfile, $quality);
// release the memory
imagedestroy($tmpimg);
imagedestroy($img);
Aquí hay un fragmento de los documentos de php.net que he usado en un proyecto y funciona bien:
<? function fastimagecopyresampled (&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 3) { // Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled. // Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled". // Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting. // Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain. // // Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero. // Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect. // 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized. // 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3. // 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster. // 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images. // 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled. if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; } if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) { $temp = imagecreatetruecolor ($dst_w * $quality + 1, $dst_h * $quality + 1); imagecopyresized ($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h); imagecopyresampled ($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality); imagedestroy ($temp); } else imagecopyresampled ($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); return true; } ?>
http://us.php.net/manual/en/function.imagecopyresampled.php#77679
fuente
$quality + 1
con($quality + 1)
? Tal como está, solo está cambiando el tamaño con un píxel adicional inútil. ¿Dónde está el cheque para cortocircuito cuando$dst_w * $quality
es>$src_w
?imagecopyresized()
. Básicamente, primero se cambia el tamaño de la imagen a un tamaño manejable (final dimensions
multiplicado porquality
), luego se vuelve a muestrear, en lugar de simplemente volver a muestrear la imagen a tamaño completo. Se puede dar lugar a una imagen final de menor calidad, pero utiliza muchos menos recursos para las imágenes más grandes queimagecopyresampled()
solo como el algoritmo de remuestreo solamente tiene que lidiar con una imagen del tamaño de 3x las dimensiones finales por defecto, en comparación con la imagen de tamaño completo ( que puede ser mucho más grande, especialmente para fotos cuyo tamaño se cambia para miniaturas).phpThumb usa ImageMagick siempre que sea posible para la velocidad ( recurriendo a GD si es necesario) y parece almacenar en caché bastante bien para reducir la carga en el servidor. Es bastante ligero de probar (para cambiar el tamaño de una imagen, simplemente llame a phpThumb.php con una consulta GET que incluya el nombre del archivo gráfico y las dimensiones de salida), por lo que puede intentarlo para ver si satisface sus necesidades.
fuente
Para imágenes más grandes, use libjpeg para cambiar el tamaño al cargar la imagen en ImageMagick y, por lo tanto, reducir significativamente el uso de memoria y mejorar el rendimiento, no es posible con GD.
$im = new Imagick(); try { $im->pingImage($file_name); } catch (ImagickException $e) { throw new Exception(_('Invalid or corrupted image file, please try uploading another image.')); } $width = $im->getImageWidth(); $height = $im->getImageHeight(); if ($width > $config['width_threshold'] || $height > $config['height_threshold']) { try { /* send thumbnail parameters to Imagick so that libjpeg can resize images * as they are loaded instead of consuming additional resources to pass back * to PHP. */ $fitbyWidth = ($config['width_threshold'] / $width) > ($config['height_threshold'] / $height); $aspectRatio = $height / $width; if ($fitbyWidth) { $im->setSize($config['width_threshold'], abs($width * $aspectRatio)); } else { $im->setSize(abs($height / $aspectRatio), $config['height_threshold']); } $im->readImage($file_name); /* Imagick::thumbnailImage(fit = true) has a bug that it does fit both dimensions */ // $im->thumbnailImage($config['width_threshold'], $config['height_threshold'], true); // workaround: if ($fitbyWidth) { $im->thumbnailImage($config['width_threshold'], 0, false); } else { $im->thumbnailImage(0, $config['height_threshold'], false); } $im->setImageFileName($thumbnail_name); $im->writeImage(); } catch (ImagickException $e) { header('HTTP/1.1 500 Internal Server Error'); throw new Exception(_('An error occured reszing the image.')); } } /* cleanup Imagick */ $im->destroy();
fuente
De su pregunta, parece que es un poco nuevo en GD, compartiré algunas experiencias mías, tal vez esto esté un poco fuera del tema, pero creo que será útil para alguien nuevo en GD como usted:
Paso 1, validar archivo. Utilice la siguiente función para comprobar si el
$_FILES['image']['tmp_name']
archivo es válido:function getContentsFromImage($image) { if (@is_file($image) == true) { return file_get_contents($image); } else { throw new \Exception('Invalid image'); } } $contents = getContentsFromImage($_FILES['image']['tmp_name']);
Paso 2, obtenga el formato del archivo Pruebe la siguiente función con la extensión finfo para verificar el formato del archivo (contenido). Diría que ¿por qué no lo usa
$_FILES["image"]["type"]
para verificar el formato de archivo? Debido a que SOLO verifica la extensión del archivo, no el contenido del archivo, si alguien cambia el nombre de un archivo originalmente llamado world.png a world.jpg ,$_FILES["image"]["type"]
devolverá jpeg no png, por lo que$_FILES["image"]["type"]
puede devolver un resultado incorrecto.function getFormatFromContents($contents) { $finfo = new \finfo(); $mimetype = $finfo->buffer($contents, FILEINFO_MIME_TYPE); switch ($mimetype) { case 'image/jpeg': return 'jpeg'; break; case 'image/png': return 'png'; break; case 'image/gif': return 'gif'; break; default: throw new \Exception('Unknown or unsupported image format'); } } $format = getFormatFromContents($contents);
Paso 3, Obtener recurso GD Obtener recurso GD de los contenidos que tenemos antes:
function getGDResourceFromContents($contents) { $resource = @imagecreatefromstring($contents); if ($resource == false) { throw new \Exception('Cannot process image'); } return $resource; } $resource = getGDResourceFromContents($contents);
Paso 4, obtenga la dimensión de la imagen Ahora puede obtener la dimensión de la imagen con el siguiente código simple:
Ahora, veamos qué variable obtuvimos de la imagen original entonces:
Paso 5, calcule los argumentos de la imagen redimensionada Este paso está relacionado con su pregunta, el propósito de la siguiente función es obtener argumentos redimensionados para la función GD
imagecopyresampled()
, el código es un poco largo, pero funciona muy bien, incluso tiene tres opciones: estirar, encoger y llenar.stretch : la dimensión de la imagen de salida es la misma que la nueva dimensión que estableciste. No mantendrá la relación altura / ancho.
encogimiento : la dimensión de la imagen de salida no excederá la nueva dimensión que proporcione y mantendrá la relación altura / ancho de la imagen.
Relleno : la dimensión de la imagen de salida será la misma que la nueva dimensión que proporcione, recortará y cambiará el tamaño de la imagen si es necesario, y mantendrá la relación altura / ancho de la imagen. Esta opción es lo que necesita en su pregunta.
function getResizeArgs($width, $height, $newwidth, $newheight, $option) { if ($option === 'stretch') { if ($width === $newwidth && $height === $newheight) { return false; } $dst_w = $newwidth; $dst_h = $newheight; $src_w = $width; $src_h = $height; $src_x = 0; $src_y = 0; } else if ($option === 'shrink') { if ($width <= $newwidth && $height <= $newheight) { return false; } else if ($width / $height >= $newwidth / $newheight) { $dst_w = $newwidth; $dst_h = (int) round(($newwidth * $height) / $width); } else { $dst_w = (int) round(($newheight * $width) / $height); $dst_h = $newheight; } $src_x = 0; $src_y = 0; $src_w = $width; $src_h = $height; } else if ($option === 'fill') { if ($width === $newwidth && $height === $newheight) { return false; } if ($width / $height >= $newwidth / $newheight) { $src_w = (int) round(($newwidth * $height) / $newheight); $src_h = $height; $src_x = (int) round(($width - $src_w) / 2); $src_y = 0; } else { $src_w = $width; $src_h = (int) round(($width * $newheight) / $newwidth); $src_x = 0; $src_y = (int) round(($height - $src_h) / 2); } $dst_w = $newwidth; $dst_h = $newheight; } if ($src_w < 1 || $src_h < 1) { throw new \Exception('Image width or height is too small'); } return array( 'dst_x' => 0, 'dst_y' => 0, 'src_x' => $src_x, 'src_y' => $src_y, 'dst_w' => $dst_w, 'dst_h' => $dst_h, 'src_w' => $src_w, 'src_h' => $src_h ); } $args = getResizeArgs($width, $height, 150, 170, 'fill');
Paso 6, tamaño de imagen Uso
$args
,$width
,$height
,$format
y $ recursos llegamos desde arriba en la siguiente función y obtener el nuevo recurso de la imagen redimensionada:function runResize($width, $height, $format, $resource, $args) { if ($args === false) { return; //if $args equal to false, this means no resize occurs; } $newimage = imagecreatetruecolor($args['dst_w'], $args['dst_h']); if ($format === 'png') { imagealphablending($newimage, false); imagesavealpha($newimage, true); $transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127); imagefill($newimage, 0, 0, $transparentindex); } else if ($format === 'gif') { $transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127); imagefill($newimage, 0, 0, $transparentindex); imagecolortransparent($newimage, $transparentindex); } imagecopyresampled($newimage, $resource, $args['dst_x'], $args['dst_y'], $args['src_x'], $args['src_y'], $args['dst_w'], $args['dst_h'], $args['src_w'], $args['src_h']); imagedestroy($resource); return $newimage; } $newresource = runResize($width, $height, $format, $resource, $args);
Paso 7, obtenga nuevos contenidos , use la siguiente función para obtener contenidos del nuevo recurso GD:
function getContentsFromGDResource($resource, $format) { ob_start(); switch ($format) { case 'gif': imagegif($resource); break; case 'jpeg': imagejpeg($resource, NULL, 100); break; case 'png': imagepng($resource, NULL, 9); } $contents = ob_get_contents(); ob_end_clean(); return $contents; } $newcontents = getContentsFromGDResource($newresource, $format);
Paso 8 obtenga la extensión , use la siguiente función para obtener la extensión del formato de imagen (nota, el formato de imagen no es igual a la extensión de imagen):
function getExtensionFromFormat($format) { switch ($format) { case 'gif': return 'gif'; break; case 'jpeg': return 'jpg'; break; case 'png': return 'png'; } } $extension = getExtensionFromFormat($format);
Paso 9 guardar imagen Si tenemos un usuario llamado mike, puede hacer lo siguiente, se guardará en la misma carpeta que este script php:
$user_name = 'mike'; $filename = $user_name . '.' . $extension; file_put_contents($filename, $newcontents);
Paso 10 destruya el recurso ¡No olvide destruir el recurso GD!
o puede escribir todo su código en una clase y simplemente usar lo siguiente:
public function __destruct() { @imagedestroy($this->resource); }
CONSEJOS
Recomiendo no convertir el formato de archivo que carga el usuario, encontrará muchos problemas.
fuente
Te sugiero que trabajes algo en este sentido:
Para usar ImageMagick en segundo plano, mueva los archivos cargados a una carpeta temporal y programe un trabajo CRON que "convierta" todos los archivos a jpeg y los cambie de tamaño en consecuencia. Consulte la sintaxis de comandos en: imagemagick-procesamiento de línea de comandos
Puede indicarle al usuario que el archivo está cargado y programado para ser procesado. El trabajo CRON se puede programar para que se ejecute diariamente en un intervalo específico. La imagen de origen se puede eliminar después del procesamiento para garantizar que una imagen no se procese dos veces.
fuente
He escuchado cosas importantes sobre la biblioteca Imagick, desafortunadamente no pude instalarla en mi computadora de trabajo ni en casa (y créanme, pasé horas y horas en todo tipo de foros).
Después de esto, he decidido probar esta clase de PHP:
http://www.verot.net/php_class_upload.htm
Es genial y puedo cambiar el tamaño de todo tipo de imágenes (también puedo convertirlas a JPG).
fuente
ImageMagick es multiproceso, por lo que parece ser más rápido, pero en realidad usa muchos más recursos que GD. Si ejecutara varios scripts PHP en paralelo, todos usando GD, superarían a ImageMagick en velocidad para operaciones simples. ExactImage es menos potente que ImageMagick pero mucho más rápido, aunque no está disponible a través de PHP, tendrá que instalarlo en el servidor y ejecutarlo
exec
.fuente
Para imágenes más grandes, use phpThumb () . Aquí se explica cómo usarlo: http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/ . También funciona con imágenes corruptas de gran tamaño.
fuente