Efficient JPEG Image Resizing in PHP
在PHP中调整大图像大小的最有效方法是什么?
我目前正在使用GD函数imagecopyresampled来拍摄高分辨率图像,并将其干净地调整为可用于Web查看的尺寸(大约700像素宽乘700像素高)。
这对于较小(小于2 MB)的照片非常有用,并且整个调整大小操作在服务器上花费的时间不到一秒钟。 但是,该网站最终将为摄影师提供服务,他们可能会上传最大10 MB的图像(或最大5000x4000像素的图像)。
对大图像执行这种大小调整操作往往会大大增加内存使用量(较大的图像可能会使脚本的内存使用量超过80 MB)。 有什么方法可以使此调整大小操作更有效? 我应该使用替代图像库,例如ImageMagick吗?
现在,调整大小代码看起来像这样
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
| function makeThumbnail($sourcefile, $endfile, $thumbwidth, $thumbheight, $quality) {
// Takes the sourcefile (path/to/image.webp) and makes a thumbnail from it
// and places it at endfile (path/to/thumb.webp).
// 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); |
人们说ImageMagick更快。充其量只是比较两个库并进行度量即可。
准备1000张典型图像。
编写两个脚本-一个用于GD,一个
用于ImageMagick。
将它们都运行几次。
比较结果(总执行量
时间,CPU和I / O使用情况,结果
画面质量)。
其他人最好的东西可能对您来说不是最好的。
另外,我认为ImageMagick具有更好的API接口。
这是我在项目中使用过且工作正常的php.net文档的摘录:
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
| <?
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/zh/function.imagecopyresampled.php#77679
phpThumb尽可能使用ImageMagick来提高速度(如有必要,可以降为GD),并且似乎可以很好地缓存以减少服务器上的负载。试用起来非常轻巧(要调整图像的大小,只需使用包含图形文件名和输出尺寸的GET查询调用phpThumb.php即可),因此您可以试一下看看它是否满足您的需求。
对于较大的图像,请使用libjpeg调整ImageMagick中图像加载的大小,从而显着减少内存使用并提高性能,而GD无法实现。
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
| $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(); |
从您的疑问来看,您似乎是GD的新手,我将分享我的一些经验,
也许这是个话题,但我认为这对像您这样的GD新手会有帮助:
步骤1,验证文件。使用以下功能检查$_FILES['image']['tmp_name']文件是否为有效文件:
1 2 3 4 5 6 7 8
| 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']); |
步骤2,获取文件格式尝试使用带有finfo扩展名的以下功能来检查文件(内容)的文件格式。您会说为什么不只使用$_FILES["image"]["type"]来检查文件格式?因为它仅检查文件扩展名而不是文件内容,所以如果某人将最初称为world.webp的文件重命名为world.webp,则$_FILES["image"]["type"]将返回jpeg而不是png,因此$_FILES["image"]["type"]可能返回错误的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); |
Step.3,获取GD资源从之前的内容中获取GD资源:
1 2 3 4 5 6 7 8
| function getGDResourceFromContents($contents) {
$resource = @imagecreatefromstring($contents);
if ($resource == false) {
throw new \\Exception('Cannot process image');
}
return $resource;
}
$resource = getGDResourceFromContents($contents); |
步骤4,获取图像尺寸现在,您可以使用以下简单代码获取图像尺寸:
1 2
| $width = imagesx($resource);
$height = imagesy($resource); |
现在,让我们看看从原始图像中得到什么变量:
1 2
| $contents, $format, $resource, $width, $height
OK, lets move on |
步骤5,计算调整大小的图像参数此步骤与您的问题有关,以下函数的目的是为GD函数imagecopyresampled()获取调整大小的参数,代码虽然很长,但是效果很好,甚至有三个选择:拉伸,收缩和填充。
拉伸:输出图像的尺寸与您设置的新尺寸相同。不会保持高/宽比。
缩小:输出图像的尺寸不会超过您指定的新尺寸,并保持图像的高宽比。
填充:输出图像的尺寸将与您提供的新尺寸相同,如果需要,它将裁剪和调整图像的尺寸,并保持图像的高/宽比。此选项是您在问题中需要的。
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
| 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'); |
第6步,调整图像大小使用从上面获取的$args,$width,$height,$format和$ resource到以下函数中,并获取调整后图像的新资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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); |
步骤7,获取新内容,使用以下函数从新的GD资源获取内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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); |
步骤8获取扩展名,使用以下函数获取图像格式的扩展名(注意,图像格式不等于图像扩展名):
1 2 3 4 5 6 7 8 9 10 11 12 13
| function getExtensionFromFormat($format) {
switch ($format) {
case 'gif':
return 'gif';
break;
case 'jpeg':
return 'jpg';
break;
case 'png':
return 'png';
}
}
$extension = getExtensionFromFormat($format); |
步骤9保存图像如果我们有一个名为mike的用户,则可以执行以下操作,它将保存到与此php脚本相同的文件夹中:
1 2 3
| $user_name = 'mike';
$filename = $user_name . '.' . $extension;
file_put_contents($filename, $newcontents); |
步骤10销毁资源不要忘记销毁GD资源!
1
| imagedestroy($newresource); |
或者您可以将所有代码编写到一个类中,并只需使用以下代码:
1 2 3
| public function __destruct() {
@imagedestroy($this->resource);
} |
提示
我建议不要转换用户上传的文件格式,您会遇到很多问题。
我建议您按照以下方式工作:
对上传的文件执行getimagesize(),以检查图像类型和大小
将所有小于700x700px的上载JPEG图像保存到目标文件夹"原样"中
将GD库用于中等大小的图像(有关代码示例,请参见本文:使用PHP和GD库调整图像大小)
使用ImageMagick放大图像。如果愿意,可以在后台使用ImageMagick。
要在后台使用ImageMagick,请将上载的文件移动到一个临时文件夹,并安排一个CRON作业,该作业将所有文件"转换"为jpeg并相应地调整其大小。请参阅以下命令的语法:imagemagick-命令行处理
您可以提示用户该文件已上载并计划进行处理。 CRON作业可以安排为每天在特定间隔运行。处理后可以删除源图像,以确保不对图像进行两次处理。
ImageMagick是多线程的,因此它看起来更快,但实际上比GD使用更多的资源。如果您同时使用GD并行运行多个PHP脚本,那么它们在简单操作上的速度就会超过ImageMagick。 ExactImage的功能不如ImageMagick,但要快得多,尽管不能通过PHP使用,但必须将其安装在服务器上并通过exec运行。
我听说过有关Imagick库的重要信息,不幸的是,我无法将其安装在工作计算机上,也无法在家中安装(并且相信我,我在各种论坛上花费了数小时)。
事后总结,我决定尝试这个PHP类:
http://www.verot.net/php_class_upload.htm
这很酷,我可以调整各种图像的大小(也可以将它们转换为JPG)。
对于较大的图像,请使用phpThumb()。使用方法如下:http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/。它也适用于大尺寸损坏的图像。
|