PHP'de Verimli JPEG Görüntü Boyutlandırma


82

PHP'de büyük resimleri yeniden boyutlandırmanın en etkili yolu nedir?

Şu anda yüksek çözünürlüklü görüntüler almak ve bunları web görüntüleme için bir boyuta (kabaca 700 piksel genişlik ve 700 piksel yükseklik) temiz bir şekilde yeniden boyutlandırmak için yeniden örneklenmiş GD işlevini kullanıyorum .

Bu, küçük (2 MB'nin altındaki) fotoğraflarda harika çalışır ve tüm yeniden boyutlandırma işlemi sunucuda bir saniyeden daha kısa sürer. Bununla birlikte, site sonunda 10 MB boyutuna kadar (veya boyutu 5000x4000 piksele kadar olan görüntüler) yükleyen fotoğrafçılara hizmet verecektir.

Bu tür bir yeniden boyutlandırma işlemini büyük görüntülerle yapmak, bellek kullanımını çok büyük bir marjla artırma eğilimindedir (daha büyük görüntüler, komut dosyası için bellek kullanımını 80 MB'yi aşabilir). Bu yeniden boyutlandırma işlemini daha verimli hale getirmenin bir yolu var mı? ImageMagick gibi alternatif bir görüntü kitaplığı kullanmalı mıyım ?

Şu anda yeniden boyutlandırma kodu şuna benzer

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

Yanıtlar:


45

İnsanlar ImageMagick'in çok daha hızlı olduğunu söylüyor. En iyi ihtimalle her iki kitaplığı da karşılaştırın ve ölçün.

  1. 1000 tipik görüntü hazırlayın.
  2. İki komut dosyası yazın - biri GD için, diğeri ImageMagick için.
  3. İkisini de birkaç kez çalıştırın.
  4. Sonuçları karşılaştırın (toplam yürütme süresi, CPU ve G / Ç kullanımı, sonuç görüntü kalitesi).

Herkesin en iyisinin sizin için en iyisi olamayacağı bir şey.

Ayrıca, bence ImageMagick çok daha iyi API arayüzüne sahip.


2
Çalıştığım sunucularda GD sıklıkla RAM bitiyor ve çöküyor, ImageMagick ise asla.
Abhi Beckert

Daha fazla katılmıyorum. Çalışmak için harika bir kabus hayal ediyorum. Büyük resimler için sık sık 500 sunucu hatası alıyorum. Kuşkusuz GD kütüphanesi daha erken çökecekti. Ama yine de, bazen sadece 6Mb görüntülerden bahsediyoruz ve 500 hata sadece en kötüsü.
Single Entity

20

İşte bir projede kullandığım ve iyi çalıştığım php.net belgelerinden bir pasaj:

<?
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 için ne koyacağınızı biliyor musunuz?
JasonDavis

Değiştirmek gerekmiyor $quality + 1ile ($quality + 1)? Olduğu gibi, gereksiz bir ekstra pikselle yeniden boyutlandırıyorsunuz. Kısa devre kontrolü nerede $dst_w * $quality> ne zaman $src_w?
Walf

8
Önerilen düzenlemeden kopyala / yapıştır: Bu işlevin yazarı Tim Eckel'dir. $ Kalite + 1 doğrudur, kaliteyi değiştirmek için değil, bir piksel genişliğinde siyah kenarlıktan kaçınmak için kullanılır. Ayrıca, bu işlev imagecopyresampled ile uyumludur, bu nedenle sözdizimiyle ilgili sorular için imagecopyresampled komutuna bakın, aynıdır.
Andomar

Bu çözüm, soruda önerilen çözümden nasıl daha iyi? Hala aynı işlevlerle GD kütüphanesini kullanıyorsunuz.
TMS

1
@Tomas, aslında kullanıyor imagecopyresized(). Temel olarak, görüntüyü önce yönetilebilir bir boyuta yeniden boyutlandırmaktır (ile final dimensionsçarpılır quality), ardından tam boyutlu görüntüyü yeniden örneklemek yerine yeniden örneklemek. Bu , daha düşük kaliteli bir son görüntü ile sonuçlanabilir, ancak daha büyük görüntüler için imagecopyresampled()tek başına olduğundan çok daha az kaynak kullanır , çünkü yeniden örnekleme algoritması, tam boyutlu görüntüye ( özellikle küçük resimler için yeniden boyutlandırılan fotoğraflar için çok daha büyük olabilir ).
0b10011

12

phpThumb hız için mümkün olduğunda ImageMagick'i kullanır (gerekirse GD'ye geri döner) ve sunucudaki yükü azaltmak için oldukça iyi önbellekleme yapar. Denemek oldukça hafiftir (bir görüntüyü yeniden boyutlandırmak için, grafik dosya adını ve çıktı boyutlarını içeren bir GET sorgusuyla phpThumb.php'yi çağırmanız yeterlidir), bu nedenle ihtiyaçlarınızı karşılayıp karşılamadığını görmek için bir şans verebilirsiniz.


ancak bu, göründüğü gibi standart PHP'nin bir parçası değildir ... bu nedenle çoğu barındırmada kullanılamayacaktır :(
TMS

1
bana sadece bir php betiği gibi görünüyor sadece php gd ve imagemagick'e sahip olmanız gerekiyor
Flo

Bu aslında yüklemeniz gereken bir uzantıdan ziyade bir PHP betiğidir, bu nedenle paylaşılan barındırma ortamları için iyidir. 4000x3000 boyutlarında <1MB jpeg görüntüleri yüklemeye çalışırken "İzin verilen bellek boyutu N bayt bitti" hatasıyla karşılaşıyordum. PhpThumb (ve dolayısıyla ImageMagick) kullanmak sorunu çözdü ve koduma dahil edilmesi çok kolay oldu.
w5m

10

Daha büyük görüntüler için ImageMagick'teki görüntü yükünde yeniden boyutlandırmak için libjpeg kullanın ve böylece bellek kullanımını önemli ölçüde azaltın ve performansı artırın, GD ile bu mümkün değildir.

$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

Sorunuza göre GD'de yeni gibisiniz, bazı deneyimlerimi paylaşacağım, belki bu biraz konu dışı, ancak sizin gibi GD'ye yeni olan birine yardımcı olacağını düşünüyorum:

Adım 1, dosyayı doğrulayın. $_FILES['image']['tmp_name']Dosyanın geçerli dosya olup olmadığını kontrol etmek için aşağıdaki işlevi kullanın :

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

Adım 2, dosya formatını alın Dosyanın (içerik) dosya formatını kontrol etmek için finfo uzantılı aşağıdaki işlevi deneyin. Neden sadece $_FILES["image"]["type"]dosya formatını kontrol etmiyorsunuz ? O Çünkü SADECE birisi başlangıçta adlı bir dosyayı yeniden adlandırmak, dosya uzantısı, dosya içeriklerini kontrol World.png için world.jpg , $_FILES["image"]["type"]png değil jpeg dönecektir böylece $_FILES["image"]["type"]yanlış sonuç döndürebilir.

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

Adım 3, GD kaynağı alın Daha önce sahip olduğumuz içeriklerden GD kaynağı alın:

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

Adım 4, görüntü boyutunu elde edin Şimdi aşağıdaki basit kodla görüntü boyutunu elde edebilirsiniz:

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

Şimdi, orijinal görüntüden hangi değişkeni aldığımızı görelim.

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

Adım 5, yeniden boyutlandırılmış görüntü bağımsız değişkenlerini hesaplayın Bu adım sorunuzla ilgilidir, aşağıdaki işlevin amacı GD işlevi için yeniden boyutlandırma bağımsız değişkenleri elde imagecopyresampled()etmektir, kod biraz uzun, ancak harika çalışıyor, hatta üç seçeneği var: genişlet, küçült ve doldurun.

streç : çıktı görüntüsünün boyutu, ayarladığınız yeni boyutla aynıdır. Yükseklik / genişlik oranını korumaz.

küçültme : çıktı görüntüsünün boyutu verdiğiniz yeni boyutu aşmaz ve görüntü yükseklik / genişlik oranını korur.

fill : çıktı görüntüsünün boyutu, verdiğiniz yeni boyutla aynı olacaktır, gerekirse görüntüyü kırpıp yeniden boyutlandıracak ve görüntü yükseklik / genişlik oranını koruyacaktır. Bu seçenek, sorunuzda ihtiyacınız olan şeydir.

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

Adım 6, boyutlandırma görüntü Kullanımı $args, $width, $height, $formatve aşağıdaki fonksiyonu içine yukarıdan var ve Boyutu değiştirilen görüntünün yeni bir kaynak olsun $ kaynak:

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

Adım 7, yeni içerikler alın, Yeni GD kaynağından içerik almak için aşağıdaki işlevi kullanın:

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

Adım 8 uzantı edinin, görüntü biçiminden uzantı almak için aşağıdaki işlevi kullanın (not, görüntü biçimi görüntü uzantısına eşit değildir):

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

Adım 9 görüntüyü kaydetme Mike adında bir kullanıcımız varsa, aşağıdakileri yapabilirsiniz, bu php komut dosyasıyla aynı klasöre kaydedilecektir:

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

Adım 10 kaynağı yok edin GD kaynağını yok etmeyi unutmayın!

imagedestroy($newresource);

veya tüm kodunuzu bir sınıfa yazabilir ve aşağıdakileri kullanabilirsiniz:

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

İPUÇLARI

Kullanıcının yüklediği dosya formatını dönüştürmemenizi öneririm, birçok sorunla karşılaşırsınız.


4

Bu satırlarda bir şeyler çalışmanızı öneririm:

  1. Görüntü türünü ve boyutunu kontrol etmek için yüklenen dosyada bir getimagesize () gerçekleştirin
  2. 700x700 pikselden küçük yüklenen tüm JPEG görüntülerini "olduğu gibi" hedef klasöre kaydedin
  3. Orta boyutlu resimler için GD kitaplığını kullanın (kod örneği için bu makaleye bakın: PHP ve GD Kitaplığı Kullanarak Görüntüleri Yeniden Boyutlandırın )
  4. Büyük resimler için ImageMagick'i kullanın. İsterseniz ImageMagick'i arka planda kullanabilirsiniz.

ImageMagick'i arka planda kullanmak için, yüklenen dosyaları geçici bir klasöre taşıyın ve tüm dosyaları jpeg'e "dönüştüren" ve buna göre yeniden boyutlandıran bir CRON işi zamanlayın. Komut sözdizimine bakın: imagemagick-komut satırı işleme

Kullanıcıya dosyanın yüklendiğini ve işlenmek üzere programlandığını bildirebilirsiniz. CRON işi, belirli bir aralıkta günlük olarak çalışacak şekilde programlanabilir. Bir görüntünün iki kez işlenmemesini sağlamak için kaynak görüntü işlendikten sonra silinebilir.


3. nokta için herhangi bir sebep göremiyorum - orta büyüklükte GD'yi kullanın. Neden onlar için ImageMagick'i de kullanmıyorsunuz? Bu, kodu çok basitleştirir.
TMS

Cron'dan çok daha iyi, cron işinin başlamasını beklemek yerine yeniden boyutlandırmanın anında başlayabilmesi için inotifywait kullanan bir betiktir.
ColinM

3

Imagick kitaplığı hakkında büyük şeyler duydum, ne yazık ki onu iş bilgisayarıma ve ne de evde kuramadım (ve bana güven, her türlü forumda saatlerimi harcadım).

Sonrasında, bu PHP sınıfını denemeye karar verdim:

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

Oldukça havalı ve her türlü resmi yeniden boyutlandırabilirim (bunları JPG'ye de dönüştürebilirim).


3

ImageMagick çok iş parçacıklıdır, bu nedenle daha hızlı görünmektedir, ancak aslında GD'den çok daha fazla kaynak kullanır. Tümü GD'yi kullanarak paralel olarak birkaç PHP betiği çalıştırırsanız, basit işlemler için ImageMagick'in hızını aşarlar. ExactImage, ImageMagick'ten daha az güçlüdür, ancak çok daha hızlıdır, ancak PHP ile mevcut olmasa da, onu sunucuya kurmanız ve çalıştırmanız gerekir exec.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.