Thay đổi kích thước hình ảnh JPEG hiệu quả trong PHP


82

Cách hiệu quả nhất để thay đổi kích thước hình ảnh lớn trong PHP là gì?

Tôi hiện đang sử dụng chức năng GD được lấy mẫu hình ảnh để chụp ảnh có độ phân giải cao và thay đổi kích thước chúng thành kích thước để xem web (chiều rộng khoảng 700 pixel x chiều cao 700 pixel).

Điều này hoạt động tốt trên các ảnh nhỏ (dưới 2 MB) và toàn bộ thao tác thay đổi kích thước chỉ mất chưa đầy một giây trên máy chủ. Tuy nhiên, trang web cuối cùng sẽ phục vụ các nhiếp ảnh gia có thể tải lên hình ảnh có kích thước tối đa 10 MB (hoặc hình ảnh có kích thước tối đa 5000x4000 pixel).

Thực hiện loại thao tác thay đổi kích thước này với hình ảnh lớn có xu hướng làm tăng mức sử dụng bộ nhớ lên rất lớn (hình ảnh lớn hơn có thể tăng mức sử dụng bộ nhớ cho tập lệnh vượt quá 80 MB). Có cách nào để thực hiện thao tác thay đổi kích thước này hiệu quả hơn không? Tôi có nên sử dụng thư viện hình ảnh thay thế như ImageMagick không?

Ngay bây giờ, mã thay đổi kích thước trông giống như thế này

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

Câu trả lời:


45

Mọi người nói rằng ImageMagick nhanh hơn nhiều. Tốt nhất chỉ cần so sánh cả hai thư viện và đo lường điều đó.

  1. Chuẩn bị 1000 hình ảnh tiêu biểu.
  2. Viết hai tập lệnh - một cho GD, một cho ImageMagick.
  3. Chạy cả hai một vài lần.
  4. So sánh kết quả (tổng thời gian thực thi, sử dụng CPU và I / O, chất lượng hình ảnh kết quả).

Một cái gì đó tốt nhất với những người khác, không thể là tốt nhất cho bạn.

Ngoài ra, theo ý kiến ​​của tôi, ImageMagick có giao diện API tốt hơn nhiều.


2
Trên các máy chủ mà tôi đã làm việc, GD thường hết RAM và bị treo, trong khi ImageMagick thì không bao giờ.
Abhi Beckert

Tôi không thể không đồng ý hơn. Tôi thấy imagemagick là một cơn ác mộng để làm việc. Tôi thường xuyên gặp lỗi máy chủ 500 đối với hình ảnh lớn. Phải thừa nhận rằng thư viện GD sẽ sụp đổ sớm hơn. Tuy nhiên, đôi khi chúng ta chỉ nói đến hình ảnh 6Mb và lỗi 500 chỉ là lỗi tồi tệ nhất.
Thực thể độc thân

20

Đây là một đoạn trích từ tài liệu php.net mà tôi đã sử dụng trong một dự án và hoạt động tốt:

<?
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/ Chức năng.imagecopyresampled.php#77679


Bạn có biết bạn sẽ đặt gì cho $ dst_x, $ dst_y, $ src_x, $ src_y không?
JasonDavis

Bạn không nên thay thế $quality + 1bằng ($quality + 1)? Vì hiện tại, bạn chỉ thay đổi kích thước với một pixel bổ sung vô ích. Kiểm tra đoản mạch khi nào $dst_w * $quality> $src_w?
Walf

8
Sao chép / dán từ chỉnh sửa được đề xuất: Đây là Tim Eckel, tác giả của chức năng này. $ Quality + 1 là đúng, nó được sử dụng để tránh viền đen rộng một pixel, không làm thay đổi chất lượng. Ngoài ra, chức năng này tương thích với trình cắm thêm hình ảnh, vì vậy đối với các câu hỏi về cú pháp, hãy xem lệnh lấy mẫu hình ảnh, nó giống hệt nhau.
Andomar

giải pháp này tốt hơn giải pháp được đề xuất trong câu hỏi như thế nào? bạn vẫn đang sử dụng thư viện GD với các chức năng tương tự.
TMS

1
@Tomas, thực ra, nó cũng đang sử dụng imagecopyresized(). Về cơ bản, nó thay đổi kích thước hình ảnh thành kích thước có thể quản lý trước ( final dimensionsnhân với quality), sau đó lấy mẫu lại, thay vì chỉ đơn giản là lấy mẫu lại hình ảnh kích thước đầy đủ. Nó có thể dẫn đến hình ảnh cuối cùng có chất lượng thấp hơn, nhưng nó sử dụng ít tài nguyên hơn cho hình ảnh lớn hơn so với imagecopyresampled()một mình vì thuật toán lấy mẫu lại chỉ phải xử lý hình ảnh có kích thước gấp 3 lần kích thước cuối cùng theo mặc định, so với hình ảnh kích thước đầy đủ ( mà có thể xa lớn hơn, đặc biệt là cho các bức ảnh được thay đổi kích cỡ cho hình thu nhỏ).
0b10011

12

phpThumb sử dụng ImageMagick bất cứ khi nào có thể để tăng tốc độ (quay trở lại GD nếu cần) và có vẻ như bộ nhớ cache khá tốt để giảm tải trên máy chủ. Nó khá nhẹ để dùng thử (để thay đổi kích thước hình ảnh, chỉ cần gọi phpThumb.php với truy vấn GET bao gồm tên tệp đồ họa và kích thước đầu ra), vì vậy bạn có thể thử xem nó có đáp ứng nhu cầu của bạn hay không.


nhưng đây không phải là một phần nếu PHP chuẩn như nó có vẻ ... vì vậy nó sẽ không khả dụng trên hầu hết các máy chủ :(
TMS

1
vẻ với tôi như nó chỉ là một kịch bản php bạn chỉ cần phải có php gd và ImageMagick
Flo

Nó thực sự là một tập lệnh PHP chứ không phải là một phần mở rộng mà bạn phải cài đặt, vì vậy nó rất tốt cho các môi trường lưu trữ chia sẻ. Tôi đã gặp phải lỗi "Kích thước bộ nhớ được phép là N byte đã cạn kiệt" khi cố gắng tải lên hình ảnh jpeg <1MB với kích thước 4000x3000. Sử dụng phpThumb (và do đó ImageMagick) đã giải quyết được vấn đề và rất dễ kết hợp vào mã của tôi.
w5m

10

Đối với hình ảnh lớn hơn, sử dụng libjpeg để thay đổi kích thước khi tải hình ảnh trong ImageMagick và do đó giảm đáng kể mức sử dụng bộ nhớ và cải thiện hiệu suất, điều này không thể thực hiện được với 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

Từ bạn quesion, có vẻ như bạn là người mới đối với GD, tôi sẽ chia sẻ một số kinh nghiệm của tôi, có thể điều này hơi lạc đề, nhưng tôi nghĩ nó sẽ hữu ích cho một người mới làm quen với GD như bạn:

Bước 1, xác thực tệp. Sử dụng chức năng sau để kiểm tra xem $_FILES['image']['tmp_name']tệp có phải là tệp hợp lệ không:

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

Bước 2, lấy định dạng tệp Hãy thử chức năng sau với phần mở rộng finfo để kiểm tra định dạng tệp (nội dung) của tệp. Bạn sẽ nói tại sao bạn không chỉ sử dụng $_FILES["image"]["type"]để kiểm tra định dạng tệp? Bởi vì nó CHỈ kiểm tra phần mở rộng tệp chứ không phải nội dung tệp, nếu ai đó đổi tên tệp ban đầu được gọi là world.png thành world.jpg , $_FILES["image"]["type"]sẽ trả về jpeg không phải png, do đó $_FILES["image"]["type"]có thể trả về kết quả sai.

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

Bước 3, Nhận tài nguyên GD Lấy tài nguyên GD từ nội dung chúng ta có trước đây:

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

Bước 4, lấy kích thước hình ảnh Bây giờ bạn có thể lấy kích thước hình ảnh với mã đơn giản sau:

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

Bây giờ, hãy xem chúng ta đã nhận được biến nào từ hình ảnh ban đầu:

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

Bước 5, tính toán các đối số hình ảnh đã thay đổi kích thước Bước này liên quan đến câu hỏi của bạn, mục đích của hàm sau là lấy các đối số thay đổi kích thước cho hàm GD imagecopyresampled(), mã hơi dài, nhưng nó hoạt động tốt, thậm chí nó có ba tùy chọn: kéo dài, thu nhỏ và điền vào.

căng : kích thước của hình ảnh đầu ra giống với kích thước mới mà bạn đã đặt. Sẽ không giữ tỷ lệ chiều cao / chiều rộng.

thu nhỏ : kích thước của hình ảnh đầu ra sẽ không vượt quá kích thước mới mà bạn cung cấp và giữ nguyên tỷ lệ chiều cao / chiều rộng của hình ảnh.

fill : kích thước của hình ảnh đầu ra sẽ giống với kích thước mới mà bạn cung cấp, nó sẽ cắt và thay đổi kích thước hình ảnh nếu cần và giữ nguyên tỷ lệ chiều cao / chiều rộng của hình ảnh. Tùy chọn này là những gì bạn cần trong câu hỏi của mình.

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

Bước 6, chỉnh sửa ảnh sử dụng $args, $width, $height, $formatvà $ tài nguyên, chúng tôi nhận được từ trên vào các chức năng sau đây và nhận được tài nguyên mới của hình ảnh thay đổi kích cỡ:

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

Bước 7, tải nội dung mới , Sử dụng chức năng sau để lấy nội dung từ tài nguyên GD mới:

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

Bước 8 lấy phần mở rộng , Sử dụng chức năng sau để nhận phần mở rộng từ định dạng hình ảnh (lưu ý, định dạng hình ảnh không bằng phần mở rộng hình ảnh):

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

Bước 9 lưu hình ảnh Nếu chúng ta có một người dùng tên là mike, bạn có thể làm như sau, nó sẽ lưu vào cùng một thư mục với tập lệnh php này:

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

Bước 10 phá hủy tài nguyên Đừng quên phá hủy tài nguyên GD!

imagedestroy($newresource);

hoặc bạn có thể viết tất cả mã của mình vào một lớp và chỉ cần sử dụng như sau:

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

LỜI KHUYÊN

Tôi khuyên bạn không nên chuyển đổi định dạng tệp mà người dùng tải lên, bạn sẽ gặp nhiều vấn đề.


4

Tôi đề nghị bạn làm việc gì đó theo những dòng sau:

  1. Thực hiện getimagesize () trên tệp đã tải lên để kiểm tra loại và kích thước hình ảnh
  2. Lưu bất kỳ hình ảnh JPEG nào đã tải lên có kích thước nhỏ hơn 700x700px vào thư mục đích "nguyên trạng"
  3. Sử dụng thư viện GD cho hình ảnh kích thước trung bình (xem bài viết này để biết mẫu mã: Thay đổi kích thước hình ảnh bằng thư viện PHP và GD )
  4. Sử dụng ImageMagick cho hình ảnh lớn. Bạn có thể sử dụng ImageMagick ở chế độ nền nếu muốn.

Để sử dụng ImageMagick ở chế độ nền, hãy di chuyển các tệp đã tải lên vào một thư mục tạm thời và lên lịch công việc CRON "chuyển đổi" tất cả các tệp thành jpeg và thay đổi kích thước chúng cho phù hợp. Xem cú pháp lệnh tại: imagemagick-command line processing

Bạn có thể nhắc người dùng rằng tệp được tải lên và lên lịch để xử lý. Công việc CRON có thể được lên lịch chạy hàng ngày vào một khoảng thời gian cụ thể. Hình ảnh nguồn có thể bị xóa sau khi xử lý để đảm bảo rằng một hình ảnh không được xử lý hai lần.


Tôi không thấy lý do gì cho điểm 3 - sử dụng GD cho kích thước trung bình. Tại sao không sử dụng ImageMagick cho họ quá? Điều đó sẽ đơn giản hóa mã rất nhiều.
TMS

Tốt hơn nhiều so với cron sẽ là một tập lệnh sử dụng inotifywait để việc thay đổi kích thước sẽ bắt đầu ngay lập tức thay vì đợi công việc cron bắt đầu.
ColinM

3

Tôi đã nghe những điều quan trọng về thư viện Imagick, tiếc là tôi không thể cài đặt nó ở máy tính làm việc của mình và cả ở nhà (và tin tôi đi, tôi đã dành hàng giờ đồng hồ trên tất cả các loại diễn đàn).

Sau lời nói, tôi đã quyết định thử lớp PHP này:

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

Nó khá tuyệt và tôi có thể thay đổi kích thước tất cả các loại hình ảnh (tôi cũng có thể chuyển đổi chúng sang JPG).


3

ImageMagick là đa luồng, vì vậy nó có vẻ nhanh hơn, nhưng thực tế sử dụng nhiều tài nguyên hơn GD. Nếu bạn chạy song song một số tập lệnh PHP bằng GD thì chúng sẽ đánh bại ImageMagick về tốc độ đối với các hoạt động đơn giản. ExactImage kém mạnh hơn ImageMagick nhưng nhanh hơn rất nhiều, mặc dù không có sẵn thông qua PHP, bạn sẽ phải cài đặt nó trên máy chủ và chạy nó exec.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.