PHP Görsel İşlemleri ve Webp Oluşturma

PHP ile yazılarımıza XML okuma ve dosya oluşturma işlemi sonrasında cURL kütüphanesi ile devam etmiştik. Sırada, PHP ile temel bazı görsel işlemleri var. Aşağıda detaylarına değineceğim örnekte, elimizdeki bir görselin (gif, png veya jpg uzantılı olsun) nasıl webp olarak kopyalanabileceğine ve hatta bu kopyalama işlemini nasıl sıkıştırarak yapabileceğimize değineceğim1. Bu süreçte 2 farklı yol izleyeceğim. İlk yöntemde işlemlerin çoğu exec(), diğer yöntemde ise PHP’nin sahip olduğu imagecreatefrompng() ve imagewebp() gibi görsel formatları özelindeki fonksiyonlardan faydalanacağız. Ancak öncesinde Fast and Beautiful Modern Image Delivery Techniques başlıklı şu videoyu izlemenizi önereceğim.

Yukarıdaki videoda da sıklıkla vurgulandığı üzere, görseller veri hacmi ve trafiği bağlamında oldukça yoğun bir kullanıma sahip. Bu da beraberinde pek çok problem ve alternatif teknolojiyi getirmekte. Webp bu çözümlerden biri. Ayrıca, responsive images2 ve lazy loading3 yine sıklıkla bahsi geçen ve kullanılan diğer yöntemler. Elbette bu yazı içerisinde tüm bu çözümlere değinmek mümkün olmayacaktır. Ancak, daha önce konuya ilişkin yazdığım şu yazıları ayrıca önerebilirim.

Şimdi gelelim işleyeceğimiz konuya ve örnek işlemlere. İlk yöntemde The Web Help tarafından PHP Webp image generator and HTML code4 başlıklı yazıda paylaşılan örneğe değineceğim.

WebP

Google WebP kullanımını yaygınlaştırmak amacıyla pek çok çözümü de kullanıma sunmakta5, libwebp6‘nin bir parçası olarak cwebp7 ve gif2webp8 örnek gösterilebilir. Bahsi geçen bu araçları komut satırı aracılığıyla oldukça pratik bir şekilde kullanılabilmekte.

cwebp [options] gorsel.png|jpg -o gorsel.webp
gif2webp [options] gorsel.gif -o gorsel.webp

Peki, PHP ile bu komutu nasıl kullanabiliriz? Bu soruyu cevaplayabilmek için öncelikle exec() fonksiyonuna kısaca değinmemiz gerekiyor.

PHP exec() Fonksiyonu

exec() php dökümanımız içerisinden komut çalıştırmamızı mümkün kılan bir PHP fonksiyonudur9.

echo exec('cd ../ && ls -li')

Örneğin, yukarıdaki kod ile bir üst dizinde yer alan dosyalarımızın listesini php dökümanımız içerisinde görüntüleyebiliriz. Alacağımız dönüş aşağı yukarı şuna benzeyecektir:

54794473 -rw-r--r-- 1 user user 5366981 Sep 30 2018 wp-cli.phar

Daha detaylı bilgi için php: exec9 sayfasını inceleyebilirsiniz. Tekrar asıl konumuza dönelim. PHP exec() bizim cwebp ve gif2webp komutlarını uygulamamızı sağlayacak bir aracı görevi görecek. Nasıl mı?

PHP exec() İle WebP İşlemleri

Tekrar PHP Webp image generator and HTML code4 başlıklı yazıdaki örneğe dönelim. Örneğin ilk bölümünde görüldüğü üzere öncelikle sunucuya libwebp kütüphanesinin indirilmesi gerekiyor. Bu işlemin ardından cwebp ve gif2webp komutları da kullanılabilir hale geliyor. Şimdi, yazıda yer alan kodu adım adım inceleyelim. İlk olarak .htaccess koduna bir bakalım10 11.

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^webp/(.*)\.webp?$ webp_generator.php?file=$1 [QSA]

Yukarıdaki kod <img src="conv2webp.php?file=images/yoda.jpeg" /> gibi bir kullanımda file ile belirtilen görselin webp dizini altındaki webp formatlı varyasyonunu getirir. Aynı işlemi tarayıcınızın adres barına ilgili dosya yolunu yazarak da gerçekleştirebilirsiniz. Gelelim conv2webp.php isimli dosyanın içeriğine…

$settings_document_root = dirname(__FILE__);
$file = trim(strip_tags($_GET['file']));
if(!is_file($file)){
  echo "original file not found";
  exit;
}

$settings_document_root = dirname(__FILE__); ile dizin bilgisini alır ve $file = trim(strip_tags($_GET['file'])); ile query parametresi olan file içeriğini yine aynı adlı değişkene atarız. Ardından gelen if statement ile dosyayı file içeriğindeki dizinde ararız. Dosya burada değilse işlemi sonlandırır, dosya bulunmuş ise sonraki adıma geçeriz; dosya uzantısını almak ve webp klasörünü oluşturmak.

$file_extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
exec("mkdir -p ".escapeshellarg("webp/".dirname($file)));

cwebp ve gif2webp

Artık dosya uzantısını kullanarak ilgili komutlarımızı işleme alabiliriz. gif animasyon niteliği taşıması sebebiyle farklı bir dönüştürme işlemine tabi, bu nedenle diğer formatlardan farklı bir komut üzerinden işleme alınmakta ve sonuç olarak animated webp dosyası oluşturulmakta. -q opsiyonu ile dönüştürme işlemindeki kalite düzeyini de belirtebilmekteyiz.

if($file_extension=="png" or $file_extension=="jpg" or $file_extension=="jpeg"){
  exec("cwebp -q 80 ".escapeshellarg($settings_document_root."/".$file)." -o ".escapeshellarg($settings_document_root."/webp/".$file.".webp"));
}
if($file_extension=="gif"){
  exec("gif2webp -q 80 ".escapeshellarg($settings_document_root."/".$file)." -o ".escapeshellarg($settings_document_root."/webp/".$file.".webp"));
}

Görüldüğü üzere kaynak dosyamızın webp klasörü içerisinde webp uzantılı bir kopyası oluşturulmakta. Örneğin, images klasörü altında bulunan dosyamız gorsel.png iken, bu dosyanın webp/images/ dizini altında gorsel.png.webp şeklinde bir webp türevi oluşturulacaktır. Dönüştürme işlemini gerçekleştiren satırlarımız bunlardan ibaret. Sonraki aşamada yer alan satırlarda ilgili dosyanın webp klasörü altında webp formatlı türevinin oluşturulup oluşturulmadığı kontrol edilmekte.

if(!file_exists($settings_document_root."/webp/".$file.".webp")){
  echo "cannot generate webp file ".escapeshellarg("webp/".$file);
  exit;
}

Aşağıdaki exec işlemleri ise dosyaların belirli zaman dilimlerinde (30 gün) silinmesini sağlamakta. Bir ara not, mtime sonrasındaki değer n olarak alınır ve zaman n*24 şeklinde işlenir. Dosyaların silinmesinin ardından webp klasörü içerisindeki klasörlere bakılır ve boş olanlar silinir.

exec("find webp/ -type f -mtime +30 -delete");
exec("find webp/ -type d -empty -delete");

Dökümanın son satırları ise oluşturulan dosyanın gösterilmesini ve time() ile eklenen zaman etiketi sayesinde sonsuz yönlendirme (infinite loop) durumunun önlenmesini sağlar.

header("Location: /webp/".$file.".webp?".time());
exit;

Peki, her şey bu kadar kolay mı? Değil elbette. Çünkü cwebp ve gif2webp çoğu paylaşımlı barındırma alanında bulunmamakta ve yüklenememekte. Diğer yandan, yine paylaşımlı barındırma alanlarında exec() fonksiyonunun kullanımına güvenlik sebebiyle izin verilmemekte. Ancak, bir çözüm mevcut. Adım adım ilerleyelim. Öncelikle cwebp ve gif2webp alternatifine bakalım.

gmagick ve imagick

Çoğu paylaşımlı barındırma alanında ya aktif ya da aktifleştirilebilir olarak gmagick veya imagick kütüphanesi sunulmakta. gmagick aslında imagick modülünün performans ve kararlılık düzeyi artırılmış bir çatalı (fork)10. Barındırma alanında sadece bu modüllerden birini kullanabilmektesiniz. Benim tercihim genelde imagick phpinfo() aracılığıyla görebilirsiniz ya da PHP aracılığı ile kontrol edebiliriz. O halde yukarıdaki kodumuzu güncelleyelim.

if(extension_loaded("cwebp") || extension_loaded('gif2webp')) {
  if($file_extension=="png" or $file_extension=="jpg" or $file_extension=="jpeg"){
    exec("cwebp -q 80 ".escapeshellarg($settings_document_root."/".$file)." -o ".escapeshellarg($settings_document_root."/webp/".$file.".webp"));
  }
  if($file_extension=="gif"){
    exec("gif2webp -q 80 ".escapeshellarg($settings_document_root."/".$file)." -o ".escapeshellarg($settings_document_root."/webp/".$file.".webp"));
  }
} else if(extension_loaded("gmagick") || extension_loaded('imagick')) {
  if($file_extension=="png" or $file_extension=="jpg" or $file_extension=="jpeg" or $file_extension=="gif"){
    exec("convert ".escapeshellarg($settings_document_root."/".$file)." -quality 80 -define webp:lossless=true ".escapeshellarg($settings_document_root."/webp/".$file.".webp"));
  }
} else {
  die("Does not have any webp library!");
}

Görüldüğü if-else statement içerisinde extension_loaded() fonksiyonunu kullanarak ilgili kütüphane/modülün aktif olup olmadığına bakabiliriz. Ardından, bulunan modüle göre exec() ile komutumuzun işleme alınmasını sağlarız. Evet, güvenlik sebebiyle exec() kullanımının da sınırlandırılmış olma ihtimaline değinmiştim. Bu duruma çözüm olarak yukarıdaki kod akışını yeniden ele alabilir ve bu amaçla PHP tarafından GD11 altında yer alan imagecreatefromjpeg, imagecreatefromgif, imagecreatefrompng ve imagewebp fonksiyonlarını kullanabiliriz.

PHP: GD ve Resim İşlevleri

PHP GD ile oldukça kapsamlı görsel işlemler gerçekleştirme imkanı sunmakta. Tüm işlevleri Resim İşleme ve Resim Üretimi > GD11 üzerinden ulaşabilirsiniz. Ben bu yazı bağlamında webp işlemi için sunulan işlevlerden birkaçını kullanacağım. Tekrar yukarıda olduğu gibi kod içerisindeki adımları anlatmayacağım1 12. Yapıyı mümkün olduğu kadar aynı tutup sadece görsel oluşturma aşamasını değiştirdim. Kodumuzun son hali şöyle;

if(strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false || strpos( $_SERVER['HTTP_USER_AGENT'], ' Chrome/') !== false) {

    $settings_document_root = dirname(__FILE__);
    $file = trim(strip_tags($_GET['file']));
    $dir_name = "webp";
    $sub_dir = dirname($file);
    $sub_dir_path = $dir_name."/".$sub_dir;

    if(!is_dir($dir_name)){
      mkdir($dir_name);
    } else {
      if(!is_dir($sub_dir_path)){
        mkdir($sub_dir_path);
      }
    }

    if(!is_file($file)){
      echo "original file not found";
      exit;
    }

    function convertImageToWebP($source, $destination, $quality = 80) {
      $file_extension = pathinfo($source, PATHINFO_EXTENSION);
      if ($file_extension == 'jpeg' || $file_extension == 'jpg') {
        $image = imagecreatefromjpeg($source);
      } else if ($file_extension == 'gif') {
        $image = imagecreatefromgif($source);
      } else if ($file_extension == 'png') {
        $image = imagecreatefrompng($source);
      } else {
        die("Unsupported format!");
      }
      return imagewebp($image, $destination, $quality);
    }

    convertImageToWebP($file, "webp/".$file.".webp", 80);

    header("Location: /webp/".$file.".webp?".time());
    exit;
}

Önceki koddan farklı olarak bu kodun ilk satırında bir if statement ile webp desteği kontrol edilmekte ve elbette exec() fonksiyonları yer almamakta. Tüm bu işlemlerin ardından artık picture etiketi ile görsellerimizi çağırabiliriz.

<picture>
    <source srcset="/webp/images/landscape.jpg.webp" type="image/webp" />
    <img src="/images/landscape.jpg" />
</picture>

Son olarak, oluşturduğunuz PHP dosyasını cronjob ile belirlediğiniz periyotlarda çalıştırılmak üzere işleme alabilirsiniz.