PHPでの効率的なJPEG画像のサイズ変更


82

PHPで大きな画像のサイズを変更する最も効率的な方法は何ですか?

現在、GD関数imagecopyresampledを使用して高解像度の画像を取得し、それらをWeb表示用のサイズ(幅700ピクセル、高さ700ピクセル)にきれいにサイズ変更しています。

これは小さい(2 MB未満)写真でうまく機能し、サイズ変更操作全体はサーバー上で1秒未満で完了します。ただし、このサイトは最終的に、最大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);

回答:


45

人々はImageMagickがはるかに速いと言います。せいぜい両方のライブラリを比較してそれを測定するだけです。

  1. 1000枚の代表的な画像を用意します。
  2. 2つのスクリプトを記述します。1つはGD用、もう1つはImageMagick用です。
  3. それらの両方を数回実行します。
  4. 結果を比較します(合計実行時間、CPUとI / Oの使用量、結果の画質)。

他の誰もが最高の何かは、あなたにとって最高ではあり得ませんでした。

また、私の意見では、ImageMagickははるかに優れたAPIインターフェースを備えています。


2
私が使用したサーバーでは、GDがRAMを使い果たしてクラッシュすることがよくありますが、ImageMagickではクラッシュしません。
Abhi Beckert 2013

これ以上異議を唱えることはできません。imagemagickを使用するのは悪夢だと思います。大きな画像で500のサーバーエラーが頻繁に発生します。確かに、GDライブラリはもっと​​早くクラッシュするでしょう。しかし、それでも、6Mbの画像しか話していないことがあり、500エラーは最悪です。
単一エンティティ2016

20

これは、私がプロジェクトで使用し、正常に機能する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


$ dst_x、$ dst_y、$ src_x、$ src_yに何を入れるか知っていますか?
JasonDavis 2009

あなたは置き換えるべきではない$quality + 1($quality + 1)?現状では、無駄な余分なピクセルを使用してサイズを変更しているだけです。$dst_w * $quality>の場合、短絡のチェックはどこにあります$src_wか?
Walf 2011

8
提案された編集からコピー/貼り付け:これは、この関数の作成者であるTimEckelです。$ quality + 1は正しいです。これは、品質を変更するのではなく、1ピクセル幅の黒い境界線を回避するために使用されます。また、この関数はimagecopyresampledとプラグイン互換であるため、構文に関する質問については、imagecopyresampledコマンドを参照してください。これは同じです。
Andomar 2011

このソリューションは、質問で提案されたソリューションよりもどのように優れていますか?引き続き同じ関数でGDライブラリを使用しています。
TMS 2011

1
@Tomas、実際には、それimagecopyresized()も使用しています。基本的には、単にフルサイズの画像をリサンプリングするのではなく、最初に画像のサイズを管理可能なサイズにサイズ変更し(final dimensions× quality)、次にリサンプリングします。それことができる低品質の最終画像をもたらすが、それはより大きな画像に対してはるかに少ないリソースを使用imagecopyresampled()(リサンプリングアルゴリズムが画像のみでフルサイズの画像に比べて3倍のサイズはデフォルトで最終寸法を、対処しなければならないとして単独でこれは、特にサムネイル用にサイズ変更されている写真の場合、はるかに大きくなる可能性があります)。
0b10011 2012年

12

phpThumbは、速度を上げるために可能な限りImageMagickを使用し(必要に応じてGDにフォールバックします)、サーバーの負荷を軽減するためにかなりうまくキャッシュしているようです。試してみるのはかなり軽量なので(画像のサイズを変更するには、グラフィックファイル名と出力サイズを含むGETクエリでphpThumb.phpを呼び出すだけです)、ニーズに合っているかどうかを確認するために試してみることができます。


しかし、これは標準のPHPの場合は一部ではないようです...したがって、ほとんどのホスティングでは利用できません:(
TMS

1
php gdとimagemagickがあれば十分なのはphpスクリプトだけのように見えます
Flo

これは確かに、インストールする必要のある拡張機能ではなくPHPスクリプトであるため、共有ホスティング環境に適しています。4000x3000のサイズで1MB未満のjpeg画像をアップロードしようとすると、「許容メモリサイズNバイトが使い果たされました」というエラーが発生しました。phpThumb(およびそれによってImageMagick)を使用すると、問題が解決し、コードに非常に簡単に組み込むことができました。
w5m 2013

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に少し慣れていないようです、私は私の経験を共有します、多分これは少し外れたトピックです、しかし私はあなたのようなGDに新しい誰かに役立つと思います:

ステップ1、ファイルを検証します。次の関数を使用して、$_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"]pngではなくjpeg$_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);

ステップ3、GDリソースの取得以前のコンテンツからGDリソースを取得します。

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

ステップ4、画像の寸法を取得するこれで、次の簡単なコードで画像の寸法を取得できます。

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

それでは、元の画像から取得した変数を見てみましょう。

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

ステップ5、サイズ変更された画像引数を計算するこのステップはあなたの質問に関連しています。次の関数の目的はGD関数のサイズ変更引数を取得することimagecopyresampled()です。コードは少し長いですが、うまく機能します。ストレッチ、シュリンクの3つのオプションもあります。 、および塗りつぶします。

ストレッチ:出力画像のサイズは、設定した新しいサイズと同じです。高さ/幅の比率を維持しません。

縮小:出力画像のサイズは、指定した新しいサイズを超えず、画像の高さ/幅の比率を維持します。

塗りつぶし:出力画像のサイズは指定した新しいサイズと同じになり、必要に応じて画像を切り抜いてサイズを変更し、画像の高さ/幅の比率を維持します。このオプションはあなたがあなたの質問に必要なものです。

   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と私たちは次の関数の中に上からだとリサイズした画像の新しいリソースを取得$リソース:

   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リソースからコンテンツを取得します。

   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拡張子を取得します。次の関数を使用して、画像形式から拡張子を取得します(画像形式は画像拡張子と等しくないことに注意してください)。

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

ステップ10リソースの破棄GDリソースの破棄を忘れないでください!

imagedestroy($newresource);

または、すべてのコードをクラスに記述して、以下を使用することもできます。

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

チップ

ユーザーがアップロードするファイル形式を変換しないことをお勧めします。多くの問題が発生します。


4

私はあなたがこれらの線に沿って何かをすることを提案します:

  1. アップロードされたファイルに対してgetimagesize()を実行して、画像の種類とサイズを確認します
  2. 700x700px未満のアップロードされたJPEG画像を「現状のまま」保存先フォルダに保存します
  3. 中サイズの画像にはGDライブラリを使用します(コードサンプルについては、この記事を参照してください:PHPとGDライブラリを使用した画像のサイズ変更
  4. 大きな画像にはImageMagickを使用してください。必要に応じて、バックグラウンドでImageMagickを使用できます。

ImageMagickをバックグラウンドで使用するには、アップロードしたファイルを一時フォルダーに移動し、すべてのファイルをjpegに「変換」してそれに応じてサイズを変更するCRONジョブをスケジュールします。次のコマンド構文を参照してください。 imagemagick-コマンドライン処理

ファイルがアップロードされ、処理がスケジュールされていることをユーザーに求めることができます。CRONジョブは、特定の間隔で毎日実行するようにスケジュールできます。画像が2回処理されないように、処理後にソース画像を削除することができます。


ポイント3の理由はわかりません-中型にはGDを使用してください。それらにもImageMagickを使用しないのはなぜですか?これにより、コードが大幅に簡素化されます。
TMS 2011

cronよりもはるかに優れているのは、inotifywaitを使用するスクリプトであり、cronジョブの開始を待つのではなく、サイズ変更が即座に開始されます。
ColinM 2012

3

Imagickライブラリについて大きなことを聞​​いたことがありますが、残念ながら、仕事用のコンピュータにも自宅にもインストールできませんでした(そして、私を信じて、あらゆる種類のフォーラムに何時間も費やしました)。

あとがき、私はこのPHPクラスを試すことにしました。

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

とてもかっこいいし、あらゆる種類の画像のサイズを変更できます(JPGに変換することもできます)。


3

ImageMagickはマルチスレッドであるため、高速に見えますが、実際にはGDよりもはるかに多くのリソースを使用します。すべてGDを使用して複数のPHPスクリプトを並行して実行した場合、単純な操作でImageMagickよりも高速になります。ExactImageはImageMagickほど強力ではありませんが、はるかに高速ですが、PHPからは利用できませんが、サーバーにインストールして実行する必要がありますexec


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.