Ekin Karadeniz

C ile PNG dosyasını parse etmek

C ile PNG dosyasını parse etmek

Daha önceki yazıda dosya formatlarının çalışma mantığından bahsetmiştik. Bu yazıda C ile PNG dosyalarını doğrulayıp bazı bilgilerini elde etmeye çalışacağız. Bu sefer anlatımı kodlarla yaptık. Üzerinde çalışacağımız dosya, önceki yazı için yaptığım 10x10 boyutunda bir PNG dosyası.

Bir PNG dosyasını doğrulayıp boyutlarını elde etmeye çalışmak yüksek seviye programlama dilleri için kolaydır ki tek yapmanız gereken varolan bir kütüphane veya fonksiyonu çağırmaktır. Fakat biz işin öğrenme kısmıyla ilgilendiğimiz için primitive bir dille örnek yapalım.

Aşağıdaki örnekte dosyanın PNG formatında olup olmadığını kontrol ediyoruz.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
  // specification'da belirtilen 8 byte'lık signature'u tanımlıyoruz.
  unsigned char pngSignature[] = {137, 80, 78, 71, 13, 10, 26, 10};

  // open fonksiyonu hata oluşursa -1 döndürür.
  // başarılıysa mevcut process'a özel pozitif bir int döndürür.
  int resim = open("./resim.png", O_RDONLY);
  if (!resim) {
    puts("hata: dosya açılamadı.");
    exit(1);
  }

  // ilk 8 byte'ı aktarabileceğimiz bir değişken oluştur.
  unsigned char sign[8];

  // read fonksiyonu, okuduğu byte miktarını ssize_t olarak döndürür.
  ssize_t sonuc = read(resim, &sign, 8); // 8 byte'a kadar oku.
  if (sonuc < 1) {
    puts("hata: dosya okunamadı.");
    exit(1);
  }

  // signature ile dosyanın ilk 8 byte'ını karşılaştırıyoruz.
  // memcmp fonksiyonu, karşılaştırılan değerler eşitse 0 döndürür.
  if (memcmp(sign, pngSignature, sizeof(pngSignature)) == 0) {
    puts("bu bir geçerli png dosyası.");
    exit(0);
  } else {
    puts("hata: bu bir png dosyası değil.");
    exit(1);
  }

  close(resim); // işimiz bitince kapatmamız gerek.
  return 0;
}

Üstteki örneği biraz daha geliştirip, ekrana IHDR verilerini basan bir program yazalım.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
  unsigned char pngSignature[] = {137, 80, 78, 71, 13, 10, 26, 10};
  unsigned char chunkTypeIHDR[] = {73, 72, 68, 82};

  int resim = open("./resim.png", O_RDONLY);
  if (!resim) {
    puts("hata: dosya açılamadı.");
    exit(1);
  }

  unsigned char sign[8];  
  if (!read(resim, &sign, 8)) {
    puts("hata: dosya okunamadı.");
    exit(1);
  }

  if (memcmp(sign, pngSignature, sizeof(pngSignature)) != 0) {
    puts("hata: bu bir png dosyası değil.");
    exit(1);
  }

  // signature'den sonra ne geliyordu? önceki yazıda belirttiğim gibi IHDR yığını.
  // yığının data uzunluk bilgisi, sonraki 4 byte içerisindeydi.
  unsigned char buf_len[4];
  // read fonksiyonunu her çağırmada işaretçi, mevcut pozisyondan devam eder.
  read(resim, &buf_len, 4);

  // buffer (char array) içerisindeki birer byte'lık parçalara ayrılmış 4 byte'lık uzunluk bilgisini
  // tek bir yerde toplamamız gerekiyor. bit shifting yaparak parçaları birleştiriyoruz.
  uint32_t chunk_len = (uint8_t)buf_len[0] << 24 | (uint8_t)buf_len[1] << 16 |
                       (uint8_t)buf_len[2] << 8  | (uint8_t)buf_len[3];

  unsigned char buf_type[4];
  read(resim, &buf_type, 4);
  if (memcmp(buf_type, chunkTypeIHDR, sizeof(chunkTypeIHDR)) != 0) {
    puts("hata: yığın türü IHDR olmalıydı.");
    exit(1);
  }

  // buffer'ın data yığınını alıyoruz. biraz önce data uzunluğunu chunk_len'e aktarmıştık.
  unsigned char buf_data[chunk_len];
  read(resim, &buf_data, chunk_len);

  // IHDR yığınında neler vardı? ilk 4 byte genişlik, sonraki 4 byte yükseklik..
  // buffer'daki parçalar halindeki uzunluk bilgilerini tek bir değişkene aktarıyoruz.
  uint32_t ihdr_gen = (uint8_t)buf_data[0] << 24 | (uint8_t)buf_data[1] << 16 |
                      (uint8_t)buf_data[2] << 8  | (uint8_t)buf_data[3];

  uint32_t ihdr_yuk = (uint8_t)buf_data[4] << 24 | (uint8_t)buf_data[5] << 16 |
                      (uint8_t)buf_data[6] << 8  | (uint8_t)buf_data[7];

  // diğer birer byte'lık verileri de değişkenlere aktaralım.
  int ihdr_bit  = buf_data[8];
  int ihdr_renk = buf_data[9];
  int ihdr_skst = buf_data[10];
  int ihdr_fltr = buf_data[11];
  int ihdr_krst = buf_data[12];

  printf("Resim genişliği: %d\n"
    "Resim yüksekliği: %d\n"
    "Bit derinliği: %d\n"
    "Renk türü: %d\n"
    "Sıkıştırma metodu: %d\n"
    "Filtre metodu: %d\n"
    "Karıştırma metodu: %d\n", 
    ihdr_gen, ihdr_yuk, ihdr_bit, ihdr_renk, ihdr_skst, ihdr_fltr, ihdr_krst);

  close(resim);
  return 0;
}

Dosya bilgilerini edinmeyi başardık. Bunun bir sonraki seviyesi kendimize bir PNG resim görüntüleme yazılımı yapmak olabilir. Ne dersiniz :)