C# ile WebP Kodlayıcı Geliştirme
VP8L kayıpsız WebP bit akışını doğrudan C# ile üreten kodlayıcının dönüşüm, LZ77 ve Huffman katmanlarını açıklar. Resmi libwebp ile kapsam farkları ve web yayınındaki kazanımlar incelenir.
İncelediğim kaynak kod, WebP biçimini kullanan bir sarmalayıcı veya yerel libwebp kitaplığına yapılan bir P/Invoke çağrısı değildir. Kod, RIFF kapsayıcısı içindeki VP8L kayıpsız WebP bit akışını doğrudan C# ile üretir. Görüntünün ARGB piksellerini analiz eder, uygun dönüşüm yolunu seçer, uzamsal ve renk tekrarlarını azaltır, LZ77 eşleşmelerini bulur, Huffman tablolarını kurar ve sonuç bitlerini dosya biçiminin beklediği sırayla yazar.
Bu kütüphaneyi resmi WebP kodlayıcısının kaynak kodunu ve biçim tanımını inceleyerek, kişisel web sitemin gerçek ihtiyaçlarına göre geliştirdim. Hedefim libwebp içindeki bütün kodlama seçeneklerini yeniden üretmek değildi. Kayıpsız web görsellerini alfa kanalıyla birlikte, harici WebP ikili dosyasına ve platforma özgü yerel kitaplığa ihtiyaç duymadan üretebilecek bağımsız bir C# çekirdeği oluşturmaktı.
Kütüphaneyi web sitemde aktif olarak kullanmam, kodun yalnız biçimsel olarak doğru bir WebP dosyası üretmesini değil, gerçek dağıtım koşullarında anlamlı dosya boyutu, uyumluluk ve işlem süresi sağlamasını da gerekli kıldı. Bu yönüyle çalışma, bir dosya biçimi deneyi değil, doğrudan üretim sisteminde karşılığı bulunan görüntü işleme bileşenidir.
WebP'nin ortaya çıkışı
Google, WebP'yi 30 Eylül 2010 tarihinde web üzerindeki görüntü aktarımını küçültmek amacıyla duyurdu. İlk sürüm kayıplı sıkıştırmaya odaklanıyordu ve VP8 video kodlayıcısının kare içi tahmin tekniklerini durağan görüntülere uyguluyordu. Görüntü verisi, yaklaşık 20 baytlık temel ek yük oluşturan RIFF tabanlı bir kapsayıcı içinde tutuluyordu. Google'ın ilk geniş ölçekli çalışması, web'den alınan yaklaşık bir milyon görüntü üzerinde WebP'nin dönemin JPEG kodlamasına göre belirgin boyut kazancı sağlayabildiğini gösterdi.
Kasım 2011'de kayıpsız sıkıştırma ve alfa kanalı desteği açıklandı. Bu ekleme WebP'yi yalnız fotoğraflar için JPEG alternatifi olmaktan çıkararak PNG türündeki grafikler, ekran görüntüleri, ikonlar ve saydam görseller için de kullanılabilir hale getirdi. Kayıplı RGB ile kayıpsız alfa kanalının aynı kapsayıcıda kullanılabilmesi de biçimin önemli tasarım özelliklerinden biri oldu.
WebP zaman içinde animasyon, ICC renk profili, EXIF ve XMP metadata desteği taşıyan genişletilmiş kapsayıcı yapısına ulaştı. Kasım 2024'te yayımlanan RFC 9649, WebP kapsayıcısını, kayıplı VP8 ve kayıpsız VP8L akışlarını ve image/webp medya türünü toplu biçimde tanımladı. RFC bilgilendirici statüdedir, ancak biçimin birlikte çalışabilirlik esaslarını güncel ve merkezi bir belgede birleştirir.
Benim gerçeklenimim bu geniş ailenin belirli bir bölümünde konumlanır:
Tek kareli görüntü üretir.
Kayıpsız VP8L bit akışını kullanır.
ARGB ve alfa bilgisini korur.
Basit RIFF ve VP8L düzenini oluşturur.
Kayıplı VP8 kodlama yapmaz.
Animasyon, ICC, EXIF ve XMP için VP8X kapsayıcısı üretmez.
Bu kapsam, web sitemde kullandığım grafik ve saydam görseller için yeterlidir. Kütüphaneyi genel amaçlı bir libwebp alternatifi haline getirecek özellikleri eklemek yerine, ihtiyaç duyduğum kodlama yolunu anlaşılır ve denetlenebilir biçimde gerçekledim.
Entropiye göre dönüşüm seçimi
WebP kayıpsız kodlama, görüntüyü doğrudan Huffman kodlarına dönüştürmez. Önce piksel dağılımındaki düzeni daha kolay sıkıştırılabilecek bir gösterime taşır. Resmi VP8L tanımı dört geri döndürülebilir dönüşüm içerir:
- Uzamsal tahmin
- Renk dönüşümü
- Yeşil kanalı çıkarma
- Renk indeksleme
Bu dönüşümler komşu pikseller ve renk kanalları arasındaki korelasyonu azaltarak kalan sembollerin Shannon entropisini düşürmeyi amaçlar.
Kendi kodumda dönüşüm seçimini görüntü üzerinde oluşturduğum histogramlarla yapıyorum. Kırmızı, yeşil, mavi ve alfa kanallarının yanı sıra ardışık piksel farklarını, kırmızı ile yeşil arasındaki farkı, mavi ile yeşil arasındaki farkı ve bunların fark görüntüsündeki karşılıklarını ayrı ayrı ölçüyorum.
Bir histogramın entropisi şu ilişkiye göre hesaplanır:
\[
H(X)=-\sum_i p_i\log_2p_i
\]Dağılım az sayıda değerde yoğunlaşıyorsa entropi düşer. Semboller geniş alana ve birbirine yakın sıklıklarla yayılıyorsa entropi yükselir. Düşük entropi, Huffman kodlamasının sık kullanılan değerleri daha kısa bit dizileriyle gösterebilmesi için uygun bir zemin oluşturur.
Kodlayıcı beş temel yolu karşılaştırır:
\[
E_{\text{ham}}=H(R)+H(G)+H(B)+H(A)
\]
\[
E_{\text{palet}}=H(P)
\]
\[
E_{\text{yeşil}}=H(R-G)+H(G)+H(B-G)+H(A)
\]
\[
E_{\text{tahmin}}=H(\Delta R)+H(\Delta G)+H(\Delta B)+H(\Delta A)
\]
\[
E_{\text{birleşik}}=
H(\Delta R-\Delta G)+H(\Delta G)+
H(\Delta B-\Delta G)+H(\Delta A)
\]En düşük tahmini entropiyi veren yol seçilir. Bu karar tam dosya boyutunun önceden hesaplanması değildir. Dönüşüm başlıklarının, Huffman tablolarının ve LZ77 eşleşmelerinin kesin bit maliyetini içermez. Buna karşılık görüntüyü her seçenekle bütünüyle kodlamadan makul bir karar verebilen hızlı bir kestirimdir.
Bu yaklaşım, resmi kodlayıcıda görülen farklı sıkıştırma yapılandırmalarını deneyip maliyetlerini karşılaştırma düşüncesinin daha küçük ve amaca yönelik bir karşılığıdır. Güncel libwebp kayıpsız kodlayıcısı da palet, yeşil çıkarma, uzamsal tahmin ve renk dönüşümü gibi farklı yapılandırmaları değerlendirir. Benim C# gerçeklenimim bunların seçilmiş alt kümesini daha sınırlı bir karar modeliyle uygular.
Palet ve renk korelasyonu
Görüntüde en fazla 256 farklı ARGB değeri varsa bir renk tablosu oluşturulur. Her özgün piksel, renk tablosundaki indisine dönüştürülür. Renk sayısı çok azsa birden fazla indis aynı baytın yeşil kanalına paketlenir:
En fazla 2 renk için sekiz piksel
En fazla 4 renk için dört piksel
En fazla 16 renk için iki piksel
Daha fazla renk için bir piksel
Bu düzen resmi WebP renk indeksleme dönüşümüyle aynıdır. Küçük paletlerde piksellerin aynı bayt içinde paketlenmesi yalnız bit sayısını düşürmez. Komşu indisleri ortak bir sembole dönüştürerek entropi kodlamasına aritmetik kodlamaya benzer bir ortak dağılım avantajı sağlar.
Palet de ham renklerden oluşan bağımsız bir liste halinde yazılmaz. İlk renk doğrudan, sonraki renkler ise önceki renk ile aralarındaki kanal farkları olarak kodlanır. Benzer renklerin ardışık bulunduğu bir tabloda bu farklar küçük bir değer alanında toplanabilir.
Yeşil çıkarma dönüşümü başka bir görüntü özelliğinden yararlanır. Doğal ve yapay görüntülerde RGB kanalları çoğu zaman bağımsız değildir. Kırmızı ve mavi değerleri yeşille birlikte hareket edebilir. Kodlayıcı bu durumda:
\[ R'=R-G \]
\[ B'=B-G \]
değerlerini saklar. Yeşil değişmeden kalır. Kod çözme sırasında yeşil yeniden kırmızı ve maviye eklenir. İşlem modüler byte aritmetiğiyle yürüdüğü için tamamen tersinirdir. Resmi WebP tanımı, tam renk dönüşümüyle ifade edilebilecek bu işlemi ek veri gerektirmediği için ayrı ve daha kısa bir dönüşüm olarak tanımlar.
Blok tabanlı uzamsal tahmin
Görüntülerdeki en güçlü fazlalıklardan biri komşu pikseller arasındaki ilişkidir. Düz bir arka planda yan yana bulunan pikseller aynı veya yakın değerlere sahiptir. Kenar boyunca değerler belirli bir yönde değişir. Ham renkleri kodlamak yerine komşulardan bir tahmin üretip yalnız farkı kodlamak, sembol dağılımını sıfır çevresinde yoğunlaştırabilir.
VP8L, sol, üst, sol üst ve sağ üst komşuları kullanan 14 tahmin modu tanımlar. Bunların arasında sabit siyah, sol piksel, üst piksel, farklı ortalamalar, seçici tahmin ve sınırlandırılmış doğrusal tahminler bulunur.
Benim gerçeklenimimde görüntü kare bölgelere ayrılır. Başlangıçta 16 x 16 bloklar kullanılır. Görüntü çok büyükse blok boyutu artırılarak toplam blok sayısı yaklaşık iki binin altında tutulur. Bu karar, büyük görüntülerde tahmin modu aramasının kontrolsüz biçimde büyümesini engeller.
Her blok için 14 tahmin modunun tamamını denerim. Her aday modda gerçek piksel ile tahmin arasındaki ARGB farkları hesaplanır ve dört kanalın histogram entropileri ölçülür. En düşük toplam entropiyi üreten mod o blok için seçilir.
Burada yalnız yerel blok histogramını değerlendirmiyorum. Daha önce işlenmiş bloklardan biriken histogramlar aday bloğun değerleriyle birleştirilir. Böylece seçim, tek başına blok içindeki hatayı değil, bütün akışta oluşacak sembol dağılımını dikkate alır. Yerel olarak biraz daha yüksek hata üreten bir tahmin, küresel Huffman dağılımını daha düzenli hale getiriyorsa tercih edilebilir.
Seçilen tahmin modları ayrı bir düşük çözünürlüklü görüntü olarak kodlanır. Asıl pikseller ise tahmin artıkları halinde tutulur. WebP'nin önemli tasarım özelliklerinden biri burada görülür: kodlama parametrelerini taşıyan yan görüntüler de aynı kayıpsız görüntü kodlama mekanizmasıyla sıkıştırılır.
Tahmin modu sayısı sabit olduğu için piksel sayısı N açısından temel maliyet doğrusaldır:
\[
T_{\text{tahmin}}(N)=\Theta(14N)=\Theta(N)
\]Ancak 14 sabitinin uygulamada önemi büyüktür. Her aday için kanal farkları, histogram kopyaları ve logaritmik entropi hesapları yapılır. Blok sayısını sınırlayan uyarlamalı boyutlandırma bu sabit maliyeti denetim altında tutar.
LZ77 ve iki boyutlu uzaklık
Dönüştürülmüş görüntü hala piksel dizisidir. Kodlayıcı ikinci aşamada daha önce görülen piksel dizilerini arar. Aynı dizi bulunduğunda renkleri yeniden yazmak yerine uzunluk ve geriye doğru uzaklık çifti üretir.
Bu, LZ77 sözlük sıkıştırmasının görüntüye uygulanmış biçimidir:
\[
(\text{uzunluk},\text{uzaklık})
\]Resmi VP8L yapısında eşleşme uzunluğu en fazla 4096 pikseldir. Uzaklık değerleri yalnız doğrusal indis farkı olarak kodlanmaz. Yakın satır ve sütunlardaki konumlara küçük kodlar veren iki boyutlu bir uzaklık eşlemesi kullanılır. Bunun nedeni görüntülerdeki tekrarların çoğunlukla uzamsal olarak yakın konumlarda bulunmasıdır.
Kendi arama çekirdeğimde üç ardışık pikselden özel bir hash değeri üretirim. Aynı hash kovasına düşen önceki konumlar bağlı zincirde tutulur. Yeni bir konum için en fazla 100 eski aday incelenir ve eşleşme uzunluğu en fazla 4096 pikselle sınırlandırılır.
Bu tasarımın üç önemli sonucu vardır:
Bütün geçmiş görüntü her konumda yeniden taranmaz.
Aşırı tekrarlı veride aday sayısı sınırsız büyümez.
Bir görüntü için arama süresinin üst sınırı öngörülebilir kalır.
Genel gösterimde, aday sınırı C ve eşleşme sınırı L olmak üzere maliyet:
\[
O(NCL)
\]şeklindedir. Kaynak kodda C=100 ve L=4096 sabit olduğu için görüntü boyutuna göre asimptotik davranış doğrusaldır. Sabitlerin yüksek olabileceğini gözden kaçırmamak gerekir. Gerçek başarım, hash dağılımına ve eşleşmelerin ne kadar erken sonlandığına bağlıdır.
Kodlayıcı en uzun bulduğu eşleşmeyi açgözlü biçimde seçer. Resmi libwebp kodlayıcısındaki maliyet tabanlı geriye dönük referans optimizasyonlarının tamamını uygulamaz. Daha kısa fakat daha yakın bir eşleşmenin bit maliyeti daha düşük olabileceği durumları kapsamlı biçimde araştırmaz. Bu tercih kodlama yoğunluğunun bir bölümünden vazgeçerek arama çekirdeğini küçük ve tahmin edilebilir tutar.
Huffman bit akışının kurulması
LZ77 aşamasından sonra akış üç tür öğe içerir:
Ham ARGB piksel
Uzunluk ve uzaklık referansı
Etkinse renk önbelleği indisi
VP8L bunları beş ayrı prefix koduyla kodlar:
- Yeşil kanal, eşleşme uzunluğu ve renk önbelleği
- Kırmızı kanal
- Mavi kanal
- Alfa kanalı
- Eşleşme uzaklığı
Kütüphanemde her alfabe için histogram oluşturulur. Ağırlıklardan bir Huffman ağacı kurulur ve ağaç derinliklerinden kanonik kodlar üretilir. Kanonik gösterimde bit örüntülerinin tamamını dosyaya yazmak gerekmez. Sembol sırası ile kod uzunlukları bilindiğinde kodlar yeniden oluşturulabilir.
Kod uzunlukları da ham olarak aktarılmaz. Tekrarlanan uzunluklar ve uzun sıfır dizileri 16, 17 ve 18 numaralı özel sembollerle çalışma uzunluğu kodlamasına tabi tutulur. Daha sonra bu kod uzunluğu sembolleri için ikinci bir Huffman ağacı oluşturulur. Böylece asıl sıkıştırma tablolarının tanımı da sıkıştırılmış olur.
Bu katman, kaynak kodun basit bir görüntü dönüştürücünün ötesine geçtiği noktadır. Geçerli bir WebP dosyası üretmek için yalnız doğru pikselleri hesaplamak yeterli değildir. Bitlerin en düşük anlamlı bitten başlanarak yazılması, basit ve normal Huffman tablolarının ayrılması, ek uzunluk bitlerinin doğru yerde bulunması ve RIFF parça boyutlarının kodlama sonunda geriye yazılması gerekir.
Kütüphane herhangi bir hedef Stream nesnesine çıktı verebilir. Ancak RIFF ve VP8L uzunluklarının sonradan başlığa yazılması gerektiği için seek edilemeyen veya doğrudan kullanılmayan hedeflerde çıktı önce bellek akışında tamamlanır, ardından hedefe kopyalanır. API akış tabanlıdır fakat kodlama süreci tam anlamıyla sabit bellekli akış kodlaması değildir.
Resmi kodlayıcıdan ayrıldığı yer
Kaynak kodum VP8L biçiminin temel katmanlarını gerçekler, ancak güncel libwebp kayıpsız kodlayıcısıyla aynı arama uzayına sahip değildir. Resmi kitaplıkta farklı efor seviyeleri, çapraz renk dönüşümü, değişken histogram bölgeleri, meta-Huffman görüntüleri, renk önbelleği seçimi, gelişmiş palet sıralamaları, near-lossless seçenekleri ve daha kapsamlı geriye dönük referans maliyet analizleri bulunur. Resmi proje, WebP kodlama ve çözme kitaplığının yanında cwebp ve dwebp araçlarını da sağlar.
Benim kodumda:
Tam çapraz renk dönüşümü uygulanmaz.
Tek bir küresel Huffman grubu kullanılır.
Meta-Huffman görüntüsü oluşturulmaz.
Renk önbelleği sınıfı ve bit akışı desteği bulunur, ancak aktif analiz sonucu önbellek bit sayısını sıfır seçer.
LZ77 ayrıştırması sınırlı hash zinciri ve en uzun eşleşme yaklaşımıyla yürür.
Basit kayıpsız VP8L kapsayıcısına odaklanılır.
Bu eksikler biçim uyumsuzluğu anlamına gelmez. WebP dönüşümleri ve renk önbelleği isteğe bağlıdır. Kodlayıcı bütün sıkıştırma olanaklarını kullanmadan da geçerli ve eksiksiz çözülebilen bir VP8L akışı üretebilir. Fark, dosyanın geçerliliğinde değil, ulaşılabilecek en küçük boyut ile kodlama süresi arasındaki dengededir.
Kütüphanenin doğru konumu bu nedenle tam libwebp yeniden yazımı değil, web varlıkları için geliştirilmiş bağımsız ve yönetilen bir kayıpsız WebP kodlayıcısıdır.
Web sitesindeki getirisi
WebP kodlayıcısını kendi C# altyapımda geliştirmemin ilk getirisi dağıtım denetimi oldu. WebP bit akışını üretmek için işletim sistemine göre farklı bir yerel kitaplık yüklemek, P/Invoke imzalarını yönetmek veya harici bir dönüştürücü süreç başlatmak gerekmedi. Kodlama davranışı uygulamanın kendi kaynak kodu içinde görünür ve denetlenebilir kaldı.
İkinci getiri, görüntülerin hedef kullanımına göre karar verebilmekti. Az renkli grafiklerde palet dönüşümü, kanalları ilişkili görüntülerde yeşil çıkarma, uzamsal olarak düzenli görüntülerde tahmin dönüşümü kullanılabiliyor. Tek bir sabit sıkıştırma yolu yerine görüntünün istatistiksel yapısına göre seçim yapılıyor.
Üçüncü getiri aktarım boyutudur. Google'ın genel WebP verileri, kayıpsız WebP'nin PNG'ye göre ortalama yüzde 26 daha küçük olabildiğini belirtir. On iki bin saydam web görüntüsü üzerinde yapılan ayrı çalışmada de WebP'nin boyut açısından optimize edilmiş PNG'lerden çoğu örnekte daha yoğun sıkıştırma sağladığı raporlanmıştır. Bu oranlar her görüntü için garanti değildir. Sonuç, içerik türüne ve kodlayıcı ayarlarına bağlıdır.
Daha küçük görüntü dosyaları web sayfasında taşınan byte miktarını, sunucu çıkış trafiğini ve istemci önbelleğinin kapladığı alanı azaltır. Özellikle makale listelerinde aynı anda çok sayıda görselin sunulduğu sayfalarda tek bir dosyadaki küçük kazanç bütün sayfanın toplamında anlamlı hale gelir. WebP'nin temel geliştirme amacı da daha küçük ve zengin görüntülerle web aktarımını hızlandırmaktır.
Basit VP8L çıktısının metadata taşımaması web yayını açısından ayrıca sade bir sonuç üretir. EXIF ve XMP gibi kaynak bilgileri otomatik olarak aktarılmaz. Bu durum gereksiz metadata yükünü ortadan kaldırırken, renk profili veya kaynak metadata korunması gereken profesyonel iş akışlarında açık bir sınır oluşturur.
Fotoğraf ağırlıklı içerikte kayıplı WebP daha küçük sonuç verebilir. Bu kütüphane kayıplı VP8 kodlamadığı için bütün görüntü sınıflarının tek çözümü değildir. Keskin çizgiler, metin içeren görseller, arayüz öğeleri, şemalar ve saydam grafikler ise kayıpsız yolun doğal kullanım alanlarıdır.
Kuramdan çalışan bit akışına
Nobel Akademik Yayıncılık tarafından Aralık 2022'de yayımlanan Yapay Zeka Kuramdan Uygulamaya kitabında tarafımdan yazılan "Görüntü ve Ses İşleme" bölümü, görüntünün sayısal gösterimini, iyileştirme süreçlerini ve öznitelik çıkarımını kuramsal çerçeveyle ele alıyordu. Yayınevinin kaydı, kitabı, ISBN numarasını ve yazarlar arasında yer aldığımı doğrulamaktadır.
Bu WebP kodlayıcısı aynı yaklaşımın daha düşük seviyedeki uygulamasıdır. Görüntü burada yalnız piksel dizisi değildir. Renk kanalları arasında korelasyon, komşu pikseller arasında uzamsal bağımlılık, tekrar eden bölgeler ve sembollerin olasılık dağılımları bulunan bir veri kaynağıdır.
Algoritma bu yapıyı aşamalar halinde işler:
\[
\text{ARGB görüntü}
\rightarrow
\text{istatistiksel analiz}
\rightarrow
\text{tersinir dönüşüm}
\rightarrow
\text{LZ77 referansları}
\rightarrow
\text{Huffman kodları}
\rightarrow
\text{VP8L bit akışı}
\]Kütüphanenin asıl teknik değeri, WebP uzantılı bir dosya yazabilmesinden gelmez. Resmi bir kodlayıcının arkasındaki veri modeli ve sıkıştırma kararları incelenmiş, bunlar C# üzerinde yeniden kurulmuş ve kişisel web sitemde uzun süre kullanılan gerçek bir yazılım bileşenine dönüştürülmüştür.
Genel amaçlı resmi kodlayıcının bütün karmaşıklığını taşımadan, amaca uygun dönüşümleri, sınırlı LZ77 aramasını ve kanonik Huffman kodlamasını bir araya getirir. Bu sınırlandırma hem kodun anlaşılabilir kalmasını hem de web yayın hattında ihtiyaç duyduğum kayıpsız WebP üretimini dış bağımlılık olmadan gerçekleştirmemi sağlamıştır.