PHP에서 효율적인 JPEG 이미지 크기 조정


82

PHP에서 큰 이미지의 크기를 조정하는 가장 효율적인 방법은 무엇입니까?

저는 현재 GD 기능인 imagecopyresampled를 사용하여 고해상도 이미지를 가져 와서 웹보기 용 크기 (약 너비 700 픽셀, 높이 700 픽셀)로 깔끔하게 크기를 조정하고 있습니다.

이것은 작은 (2MB 미만) 사진에서 훌륭하게 작동하며 전체 크기 조정 작업은 서버에서 1 초도 채 걸리지 않습니다. 그러나이 사이트는 결국 최대 10MB 크기의 이미지 (또는 최대 5000x4000 픽셀 크기의 이미지)를 업로드하는 사진 작가에게 서비스를 제공 할 것입니다.

큰 이미지로 이러한 종류의 크기 조정 작업을 수행하면 메모리 사용량이 매우 크게 증가하는 경향이 있습니다 (이미지가 클수록 스크립트의 메모리 사용량이 80MB를 초과 할 수 있음). 이 크기 조정 작업을보다 효율적으로 만들 수있는 방법이 있습니까? 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. 두 개의 스크립트를 작성하십시오. 하나는 GD 용이고 다른 하나는 ImageMagick 용입니다.
  3. 둘 다 몇 번 실행하십시오.
  4. 결과를 비교합니다 (총 실행 시간, CPU 및 I / O 사용량, 결과 이미지 품질).

다른 모든 사람들이 최고가 될 수있는 것이 당신에게 최고가 될 수 없습니다.

또한 제 생각에 ImageMagick은 훨씬 더 나은 API 인터페이스를 가지고 있습니다.


2
내가 함께 일한 서버에서 GD는 종종 RAM이 부족하고 충돌하는 반면 ImageMagick은 결코 그렇지 않습니다.
Abhi Beckert 2013

더 이상 동의 할 수 없습니다. 저는 imagemagick이 작업하기에 악몽이라고 생각합니다. 큰 이미지에 대해 500 개의 서버 오류가 자주 발생합니다. 분명히 GD 라이브러리는 더 일찍 충돌 할 것입니다. 그러나 여전히 우리는 때때로 6Mb 이미지에 대해서만 이야기하고 있으며 500 개의 오류는 최악입니다.
단일 법인

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

당신은 대체 안 $quality + 1($quality + 1)? 그대로, 쓸모없는 추가 픽셀로 크기를 조정하는 것입니다. 단락 검사는 어디에서 $dst_w * $quality> $src_w?
Walf

8
제안 된 편집에서 복사 / 붙여 넣기 :이 함수의 작성자 인 Tim Eckel입니다. $ quality + 1은 정확하며, 품질을 변경하지 않고 1 픽셀 너비의 검은 색 테두리를 피하는 데 사용됩니다. 또한이 함수는 imagecopyresampled와 플러그인 호환되므로 구문에 대한 질문은 imagecopyresampled 명령을 참조하십시오. 동일합니다.
Andomar

이 솔루션이 질문에서 제안 된 것보다 더 나은 방법은 무엇입니까? 여전히 동일한 기능으로 GD 라이브러리를 사용하고 있습니다.
TMS

1
@Tomas, 실제로도 사용 imagecopyresized()하고 있습니다. 기본적으로, 먼저 (관리 가능한 크기로 이미지 크기를 조정 것 final dimensions곱한 quality) 다음 을 리샘플링보다는 단순히 전체 크기 이미지를 리샘플링. 그것은 수있는 낮은 품질의 최종 이미지 발생하지만,보다 큰 이미지를 훨씬 적은 리소스를 사용 imagecopyresampled()합니다 (리샘플링 알고리즘은 이미지와 전체 크기 이미지에 비해 3 배의 크기는 기본적으로 최종 치수를 처리해야으로 혼자 특히 축소판에 맞게 크기가 조정되는 사진의 경우 훨씬 더 클 수 있습니다 .
0b10011

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

Step.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(). 코드는 다소 길지만 훌륭하게 작동하며 세 가지 옵션도 있습니다 : 늘이기, 축소 , 채우기.

stretch : 출력 이미지의 치수가 설정 한 새 치수와 동일합니다. 높이 / 너비 비율을 유지하지 않습니다.

축소 : 출력 이미지의 크기가 새로운 크기를 초과하지 않고 이미지 높이 / 너비 비율을 유지합니다.

fill : 출력 이미지의 크기는 사용자가 지정한 새 크기와 동일하며필요한 경우 이미지를 자르고 크기를 조정 하며 이미지 높이 / 너비 비율을 유지합니다. 이 옵션은 질문에 필요한 것입니다.

   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 작업은 특정 간격으로 매일 실행되도록 예약 할 수 있습니다. 이미지가 두 번 처리되지 않도록 처리 후 원본 이미지를 삭제할 수 있습니다.


요점 3에 대한 이유가 없습니다. 중간 크기의 경우 GD를 사용하십시오. ImageMagick도 사용하지 않는 이유는 무엇입니까? 그것은 코드를 많이 단순화 할 것입니다.
TMS

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.