【问题标题】:Efficient JPEG Image Resizing in PHPPHP 中高效的 JPEG 图像大小调整
【发布时间】:2008-08-15 19:55:17
【问题描述】:

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

我目前正在使用GD 函数 imagecopyresampled 来获取高分辨率图像,并将它们干净地调整为适合网络查看的大小(大约 700 像素宽 x 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);

【问题讨论】:

    标签: php image gd jpeg


    【解决方案1】:

    人们说 ImageMagick 快得多。充其量只是比较两个库并进行衡量。

    1. 准备 1000 张典型图像。
    2. 编写两个脚本 -- 一个用于 GD,一个用于 对于 ImageMagick。
    3. 同时运行它们几次。
    4. 比较结果(总执行 时间、CPU 和 I/O 使用情况、结果 图像质量)。

    别人最好的东西,对你来说不可能是最好的。

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

    【讨论】:

    • 在我使用过的服务器上,GD 经常耗尽内存并崩溃,而 ImageMagick 从来没有。
    • 我不能再反对了。我发现 imagemagick 是一场噩梦。我经常收到大图像的 500 个服务器错误。不可否认,GD 库会更早崩溃。但是,我们有时只谈论 6Mb 图像,而 500 错误只是最糟糕的。
    【解决方案2】:

    这是我在项目中使用的 php.net 文档中的一个 sn-p,并且工作正常:

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

    【讨论】:

    • 你知道你会为 $dst_x, $dst_y, $src_x, $src_y 放什么吗?
    • 你不应该用($quality + 1)替换$quality + 1吗?事实上,你只是用一个无用的额外像素调整大小。当$dst_w * $quality > $src_w 时短路检查在哪里?
    • 从建议的编辑中复制/粘贴:这是 Tim Eckel,此函数的作者。 $quality + 1 是正确的,它用于避免一个像素宽的黑色边框,而不是改变质量。还有这个函数是和imagecopyresampled插件兼容的,所以语法上的问题可以看imagecopyresampled命令,是一样的。
    • 这个解决方案比问题中提出的解决方案更好吗?您仍在使用具有相同功能的 GD 库。
    • @Tomas,实际上,它也在使用imagecopyresized()。基本上,它首先将图像调整为可管理的大小(final dimensions 乘以quality),然后对其重新采样,而不是简单地重新采样全尺寸图像。它可以导致最终图像质量较低,但它对较大图像使用的资源比单独使用imagecopyresampled() 少得多,因为重采样算法只需处理大小为最终尺寸 3 倍的图像默认值,与全尺寸图像相比(可能大,特别是对于为缩略图调整大小的照片)。
    【解决方案3】:

    phpThumb 尽可能使用 ImageMagick 以提高速度(必要时回退到 GD)并且似乎缓存得很好以减少服务器上的负载。试用它非常轻巧(要调整图像大小,只需使用包含图形文件名和输出尺寸的 GET 查询调用 phpThumb.php),因此您可以试一试,看看它是否满足您的需求。

    【讨论】:

    • 但这不是标准 PHP 的一部分,因为它看起来像......所以它在大多数主机上都不可用:(
    • 在我看来它只是一个 php 脚本,你只需要 php gd 和 imagemagick
    • 它确实是一个 PHP 脚本,而不是您必须安装的扩展程序,因此非常适合共享托管环境。尝试上传尺寸为 4000x3000 的
    【解决方案4】:

    对于较大的图像,使用 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();
    

    【讨论】:

      【解决方案5】:

      从你的问题来看,你对GD似乎有点陌生,我将分享一些我的经验, 也许这有点跑题了,但我认为这对像你这样刚接触 GD 的人会有所帮助:

      第一步,验证文件。使用以下函数检查$_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']);
      

      第 2 步,获取文件格式 尝试以下带有 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,获取GD资源从我们之前的内容中获取GD资源:

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

      第四步,获取图片尺寸现在可以通过以下简单代码获取图片尺寸:

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

      现在,让我们看看我们从原始图像中得到了什么变量:

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

      第五步,计算调整大小的图像参数 这一步和你的问题有关,下面这个函数的目的是获取GD函数imagecopyresampled()的调整大小参数,代码有点长,但是效果很好,它甚至有三个选项:拉伸、收缩和填充。

      拉伸:输出图像的尺寸与您设置的新尺寸相同。不会保持高宽比。

      收缩:输出图片的尺寸不会超过你给的新尺寸,并保持图片的高宽比。

      填充:输出图像的尺寸将与您提供的新尺寸相同,它将裁剪和调整大小 如果需要图像,并保持图像的高/宽比。 此选项是您的问题所需要的。

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

      第六步,调整图片大小 使用$args$width$height$format和我们从上面得到的$resource进入下面的函数,得到调整后的新资源图片:

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

      第七步,获取新内容,使用如下函数从新的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);
      

      第八步获取扩展名,使用如下函数获取图片格式的扩展名(注意,图片格式不等于图片扩展名):

         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 脚本相同的文件夹:

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

      第十步销毁资源别忘了销毁GD资源!

      imagedestroy($newresource);
      

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

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

      提示

      我建议不要转换用户上传的文件格式,你会遇到很多问题。

      【讨论】:

        【解决方案6】:

        我建议您按照以下方式工作:

        1. 对上传的文件执行 getimagesize( ) 以检查图像类型和大小
        2. 将所有上传的小于 700x700 像素的 JPEG 图像“按原样”保存到目标文件夹中
        3. 对中等大小的图像使用 GD 库(代码示例参见本文:Resize Images Using PHP and GD Library
        4. 对大图像使用 ImageMagick。如果您愿意,可以在后台使用 ImageMagick。

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

        您可以提示用户文件已上传并计划处理。 CRON 作业可以安排为每天以特定的时间间隔运行。处理后可以删除源图像,以确保图像不会被两次处理。

        【讨论】:

        • 我看不出第 3 点有任何理由 - 将 GD 用于中型。为什么不也为他们使用 ImageMagick 呢?这将大大简化代码。
        • 比 cron 更好的是使用 inotifywait 的脚本,以便立即开始调整大小,而不是等待 cron 作业开始。
        【解决方案7】:

        我听说过有关 Imagick 库的大事,不幸的是,我无法在我的工作计算机上安装它,也不能在家里安装它(相信我,我在各种论坛上花了好几个小时)。

        后记,我决定试试这个 PHP 类:

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

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

        【讨论】:

          【解决方案8】:

          ImageMagick 是多线程的,所以它看起来更快,但实际上比 GD 使用更多的资源。如果您使用 GD 并行运行多个 PHP 脚本,那么它们的简单操作速度会超过 ImageMagick。 ExactImage 不如 ImageMagick 强大,但速度要快得多,虽然无法通过 PHP 获得,但您必须将其安装在服务器上并通过 exec 运行它。

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-07-30
              • 2011-11-17
              • 2011-11-25
              • 2012-01-09
              • 1970-01-01
              • 2012-08-28
              相关资源
              最近更新 更多