首页 文章

PHP中高效的JPEG图像大小调整

提问于
浏览
78

在PHP中调整大图像大小的最有效方法是什么?

我目前正在使用GD函数imagecopyresampled来拍摄高分辨率图像,并将它们干净地调整到适合网页查看的大小(大约700像素宽,700像素高) .

这适用于小(2 MB以下)的照片,整个调整大小操作在服务器上只需不到一秒钟 . 但是,该网站最终将为可能上传最大10 MB图像的摄影师提供服务(或者图像尺寸最大为5000x4000像素) .

使用大图像执行此类调整大小操作往往会大幅增加内存使用量(较大的图像会使脚本的内存使用量超过80 MB) . 有没有办法让这个调整大小操作更有效率?我应该使用备用图像库,例如ImageMagick吗?

现在,调整大小代码看起来像这样

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);

9 回答

  • 2

    ImageMagick是多线程的,因此看起来更快,但实际上使用的资源比GD多得多 . 如果您使用GD并行运行多个PHP脚本,那么它们必须在服务器上安装它并通过 exec 运行它 .

  • 4

    对于较大的图像,请使用phpThumb() . 以下是如何使用它:http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/ . 它也适用于大型损坏的图像 .

  • 19

    人们说ImageMagick要快得多 . 最好只比较两个库并测量它 .

    • 准备1000张典型图像 .

    • 编写两个脚本 - 一个用于GD,一个用于ImageMagick .

    • 运行它们几次 .

    • 比较结果(总执行时间,CPU和I / O使用情况,结果图像质量) .

    其他人最好的东西,对你来说不是最好的 .

    另外,在我看来,ImageMagick有更好的API接口 .

  • 3

    这是我在项目中使用的php.net文档的一个片段,工作正常:

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

  • 11

    phpThumb尽可能使用ImageMagick速度(必要时回退到GD)并且似乎缓存非常好以减少服务器上的负载 . 尝试它是非常轻量级的(调整图像大小,只需用包含图形文件名和输出尺寸的GET查询调用phpThumb.php),这样你就可以试一试它是否符合你的需求 .

  • 10

    对于较大的图像,使用libjpeg调整ImageMagick中的图像负载大小,从而显着减少内存使用并提高性能,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();
    
  • 9

    从你的问题来看,你似乎对GD有点新意,我会分享一些我的经历,也许这有点偏离主题,但我认为对你这样的新人来说会有所帮助:

    Step 1, validate file. 使用以下函数检查 $_FILES['image']['tmp_name'] 文件是否为有效文件:

    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']);
    

    Step 2, get file format 尝试使用finfo扩展名的以下函数来检查文件的文件格式(内容) . 你会说为什么不用 $_FILES["image"]["type"] 检查文件格式?因为它检查文件扩展名不是文件内容,如果有人将原来名为world.png的文件重命名为world.jpg, $_FILES["image"]["type"] 将返回jpeg而不是png,因此 $_FILES["image"]["type"] 可能返回错误的结果 .

    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, Get GD resource 从之前的内容中获取GD资源:

    function getGDResourceFromContents($contents) {
          $resource = @imagecreatefromstring($contents);
          if ($resource == false) {
             throw new \Exception('Cannot process image');
          }
          return $resource;
       }
       $resource = getGDResourceFromContents($contents);
    

    Step 4, get image dimension 现在,您可以使用以下简单代码获取图像尺寸:

    $width = imagesx($resource);
      $height = imagesy($resource);
    

    Now, 让我们看看我们从原始图像中获得了什么变量:

    $contents, $format, $resource, $width, $height
           OK, lets move on
    

    Step 5, calculate resized image arguments 这一步与您的问题有关,以下函数的目的是为GD函数 imagecopyresampled() 获取调整大小参数,代码有点长,但它工作得很好,它甚至有三个选项:拉伸,收缩和填充 .

    stretch :输出图像's dimension is the same as the new dimension you set. Won' t保持高/宽比 .

    shrink :输出图像's dimension won' t超出您给出的新尺寸,并保持图像高度/宽度比 .

    fill :输出图像的尺寸与您给出的新尺寸相同,如果需要,它将为 crop & resize 图像,并保持图像高度/宽度比 . This option is what you need in your question.

    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');
    

    Step 6, resize image 使用我们从上面获得的 $args$width$height$format 和$资源到以下函数中并获取已调整大小的图像的新资源:

    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);
    

    Step 7, get new contents ,使用以下函数从新的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);
    

    Step 8 get extension ,使用以下函数从图像格式获取扩展名(注意,图像格式不等于图像扩展名):

    function getExtensionFromFormat($format) {
          switch ($format) {
             case 'gif':
                return 'gif';
                break;
             case 'jpeg':
                return 'jpg';
                break;
             case 'png':
                return 'png';
          }
       }
       $extension = getExtensionFromFormat($format);
    

    Step 9 save image 如果我们有一个名为mike的用户,您可以执行以下操作,它将保存到与此php脚本相同的文件夹中:

    $user_name = 'mike';
    $filename = $user_name . '.' . $extension;
    file_put_contents($filename, $newcontents);
    

    Step 10 destroy resource 别忘了销毁GD资源!

    imagedestroy($newresource);
    

    或者您可以将所有代码写入类中,只需使用以下代码:

    public function __destruct() {
          @imagedestroy($this->resource);
       }
    

    TIPS

    I recommend not to convert file format that user upload, you will meet many problems.

  • 3

    我建议你按照以下方式开展工作:

    • 在上传的文件上执行getimagesize()以检查图像类型和尺寸

    • 将任何小于700x700px的上传JPEG图像保存到目标文件夹"as-is"

    • 将GD库用于中等大小的图像(有关代码示例,请参阅此文章:Resize Images Using PHP and GD Library

    • 使用ImageMagick拍摄大图像 . 如果您愿意,可以在后台使用ImageMagick .

    要在后台使用ImageMagick,请将上传的文件移动到临时文件夹,并安排一个CRON作业"convert"s将所有文件转换为jpeg并相应地调整它们的大小 . 请参阅命令语法:imagemagick-command line processing

    您可以提示用户上载和计划处理文件 . 可以安排CRON作业每天以特定间隔运行 . 处理后可以删除源图像,以确保图像不被处理两次 .

  • 44

    我听说过Imagick图书馆的大事,不幸的是我无法在我的工作电脑上安装它,也不能在家里安装(相信我,我在各种论坛上花费了数小时和数小时) .

    后来,我决定尝试这个PHP类:

    http://www.verot.net/php_class_upload.htm

    这很酷,我可以调整各种图像(我也可以将它们转换为JPG) .

相关问题