HotPDF 2.747.0, Object Pascal ile sıfırdan yazılmış bir VP8L (WebP lossless) decoder ile WebP görüntülerini çözer; böylece THotPDF.AddImageFromFile bir .webp yolunu doğrudan kabul eder, dağıtılacak libwebp DLL'i ve başlatılacak helper process gerekmez. Decoder, RFC 9649 section 3'ü bütünüyle uygular: RIFF container walk, canonical prefix code'lar, LZ77 backward reference'ları, color cache ve dört inverse transform. Lossy VP8 frame'leri yarım yamalak decode etmek yerine sesli biçimde reddedilir
Tetikleyici sıradandı. Bir design tool her asset'i modern varsayılan olduğu için WebP olarak export eder, asset'ler on yıldır PNG ve JPEG'i sorunsuz yiyen bir invoice veya catalog generator'a düşer ve bir anda girdilerin yarısı reddedilir. Bariz çözüm libwebp'e bağlanıp devam etmektir. Bariz çözüm aynı zamanda self-contained bir VCL component'i deployment hikâyesi olan bir şeye çeviren çözümdür
libwebp'e bağlanmak yerine VP8L neden uygulandı?
HotPDF codec'i Pascal ile uygular, çünkü müşterilerin kendi executable'larına derlediği bir Delphi component'i sessizce runtime DLL edinemez. Native bir dependency, izlenecek 32 bitlik ve 64 bitlik birer binary, sabitlenecek bir sürüm, deployment'ı çalıştıran kişiye açıklanacak bir code-signing zinciri ve antivirus yazılımının kilitli bir terminalde hoşlanmayabileceği bir dosya daha demektir. Ana selling point'i projeye bırakıp çalışan bir component olan bir şey için bu teorik değil, gerçek bir maliyettir. Argümanın diğer yarısı VP8L'nin küçük olmasıdır: prefix-code ve LZ77 formatı, dört inverse transform ve 120 girişli neighborhood distance map; HPDFWebP.pas içindeki bütün decoder 900 Pascal satırının altındadır. THotPDF.AddImage içinde WebP branch'i, zaten .jp2, .j2k, .jpt ve .jpc yollarını JPEG 2000 yoluna yönlendiren aynı extension dispatch içinde durur; yani plumbing zaten vardı ve Delphi'de PDF'e JPEG 2000 görüntüleri ekleme walkthrough'unda anlatılan yer de burasıdır. PDF görüntüsü yerine ham piksel isteyen çağıranlar doğrudan HPDFDecodeWebPLossless'e gidebilir; bu metod tarama satırı sırasında TWebPCardinalArray türünde $AARRGGBB değerleri doldurur
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp yerleşik VP8L decoder'a yönlendirilir, DLL kullanılmaz
Idx := Pdf.AddImageFromFile('product-shot.webp', icFlate);
Pdf.CurrentPage.ShowImage(Idx, 50, 500, 240, 180, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
VP8L bitstream'i neden aynı anda iki yönde okunur?
Çünkü container bit sırası ile prefix code bit sırası bağımsız tanımlanmıştır ve VP8L bunlar için zıt kuralları seçer. RFC 9649 section 3.2 açıkça bitstream'in least significant bit first okunduğunu söyler: reader bir baytın 0. bitinden başlar ve yukarı doğru ilerler. Bu stream'in içinde taşınan canonical prefix code'lar most significant bit first, ağacın kökünden başlayarak gelir; dolayısıyla decode yürüyüşü accumulator'ı sola kaydırır ve her yeni biti altta OR'lar. Reader ile code walk bu yüzden aynı loop içinde zıt yönlerde ilerler; metni yeniden okuduğunuzda her seferinde bug gibi görünmesinin nedeni budur
function TWebPBitReader.ReadBit: Integer;
begin
if BytePos >= Length(Data) then
raise EWebPDecode.Create('WebP bitstream exhausted');
Result := (Data[BytePos] shr BitPos) and 1; // LSB first, RFC 9649 3.2
Inc(BitPos);
if BitPos = 8 then
begin
BitPos := 0;
Inc(BytePos);
end;
end;
// canonical yürüyüş ters yönde ilerler: stream'den alınan ilk bit
// kodun en anlamlı bitidir
for Len := 1 to 15 do
begin
Code := (Code shl 1) or BR.ReadBit;
if Counts[Len] > 0 then
begin
if Code - First < Counts[Len] then
Exit(Symbols[Index + Code - First]);
First := (First + Counts[Len]) shl 1;
Index := Index + Counts[Len];
end
else
First := First shl 1;
end;
Stream'i sessizce senkronizasyondan çıkaran üç RFC ayrıntısı
RFC 9649'da tam bir kez söylenen, gözden kaçması kolay ve her biri tek bir bit kazandıran veya kaybettiren üç semantik vardır; bu, daha sonraki her tabloyu gürültüye çevirmeye yeter. Üçü de HotPDF VP8L decoder'ında bulundu ve üçü de aynı belirtıyı üretir: makul görünen ama her yerde yanlış bir görüntü
- Primary olmayan bir role'deki entropy-coded image hiç meta-prefix bit yazmaz.
entropy-coded-imageABNF'si bu öğeyi hiç içermez; bir tane okumak stream'i bir bit kaydırır. HotPDF entropy image'ın kendisi, predictor ve color transform verisi ile color-indexing palette içinAllowMeta = Falsegeçirir - Tek yapraklı prefix code sıfır bit tüketir. RFC 9649 section 3.7.2.1 bunu doğrudan söyler ve canonical walk mutlu bir şekilde bir bit okuyup sonra onu yerleştiremez; bu nedenle
BuildHufftoplam sembol sayısının 1 olduğunu algılar, ağacıSingleişaretler ve reader'a dokunmadan o tek sembolü çözer - cache_bits değeri 0, color cache boyutunun 0 olduğu anlamına gelir,
1 shl 0değil. Kullanışlı shift sonucu 1 verir; bu da green alphabet'ın256 + 24 + CacheSizedeğerini 280 yerine 281 yapar ve sonraki her prefix code table okuması hizadan çıkar
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 gerçekten none demektir
if BR.ReadBit = 1 then
begin
CacheBits := Integer(BR.ReadBits(4));
if (CacheBits < 1) or (CacheBits > 11) then
raise EWebPDecode.Create('WebP color cache bits out of range');
CacheSize := 1 shl CacheBits;
end;
// RFC 9649 3.8.3: meta prefix bitini yalnızca spatially coded (ARGB)
// image taşır; entropy-coded role'ler bunu hiç yazmaz
if AllowMeta then
UseMeta := BR.ReadBit
else
UseMeta := 0;
// ...
ReadHuffCode(256 + 24 + CacheSize, Groups[I].Green); // 281 değil 280
ReadHuffCode(256, Groups[I].Red);
ReadHuffCode(256, Groups[I].Blue);
ReadHuffCode(256, Groups[I].Alpha);
ReadHuffCode(40, Groups[I].Dist);
Bring-up sırasında kullanılan fixture'da üçü sırasıyla 47., 81. ve 89. bitlerde ortaya çıktı. Bu sayıların değeri bu bölümün asıl noktasıdır. Üçünden hiçbiri off-by-one olarak kendini bildirmedi; her biri tamamlanana kadar decode olan ama static gibi görünen bir görüntü sundu ve onları ayıran tek şey stream'in reference ile anlaşmayı bıraktığı kesin bit konumuydu
Bit-position diffing ne kazandırır?
Bit-position diffing işe yaramaz bir soruyu tek satırlık bir soruya çevirir: "bu resim neden yanlış" yerine "stream neden 81. bit'te ayrıştı". Kurulum ucuzdur. Pillow her .webp fixture'ını ve aynı görüntünün kendi decode'undan bir .rgba dump'ını yazar; Pascal probe ile küçük bir Python reference model'i her read'in yanına ilerleyen bir bit counter log'lar; iki log'un ayrıldığı ilk konum bug'ın yaşadığı yerdir. Mümkün olduğunca az şeyi kullanan bir fixture ile başlayın: yalnızca simple-code yoluna giren düz 32x32 görüntü. Onu yeşile getirin, sonra gradient'leri, tek olmayan boyutları ve alpha'yı her seferinde bir fixture ekleyin. Bit sırasını tahmin etmek ise bir günü harcama yöntemidir
Dürüst caveat şudur: reference da yanlıştı. Python model cache_bits okumayı unutmuştu ve transform loop'u tamamlanana kadar çalışmıyordu; bu nedenle bazı divergence point'leri Pascal decoder'ın değil reference decoder'ın sync kaybetmesiydi. Bir reference implementation'ın yanlış olması test edilen implementation'ı doğru yapmaz; iki taraf da şüpheden yararlanamaz ve her ayrışma RFC metnine göre karara bağlanmalıdır. O metni source'dan da çekin. Search summary'leri numeric table'ları sık sık bozar; 120 girişli distance map, 14 predictor mode ve color cache multiplier $1e35a7bd tam olarak aktarılmalıdır
Pascal integer division C'den nerede ayrılır?
VP8L color transform signed delta'lı 3.5 fixed point'tir ve Pascal ile C'nin anlaşmayı bıraktığı yer burasıdır. C negatif tamsayıları arithmetic olarak shift eder, yani floor'lar; Pascal div sıfıra doğru truncate eder. Negatif her product'ta ikisi bir fark eder; bu nedenle inverse color transform bütün görüntü boyunca piksel başına bir channel step kayar. HotPDF bu yüzden div'e güvenmek yerine FloorDiv32 içinde floor işlemini açıkça yapar
// C negatiflerde arithmetic shift ile floor yapar; Pascal div ise
// sıfıra doğru truncate eder, bu yüzden negatif durum için açık düzeltme gerekir
function FloorDiv32(V: Integer): Integer;
begin
Result := V div 32;
if (V < 0) and (V mod 32 <> 0) then
Dec(Result);
end;
// Hem transform element byte'ı hem color channel byte'ı önce sign-extend
// edilir; aralarındaki 3.5 fixed-point delta
function ColorDelta(T, C: Integer): Integer;
var
T8, C8: Integer;
begin
T8 := T;
if T8 >= 128 then
Dec(T8, 256);
C8 := C;
if C8 >= 128 then
Dec(C8, 256);
Result := FloorDiv32(T8 * C8);
end;
Bu kusur sınıfını adlandırmak gerekir, çünkü fixture'ları tesadüfen non-negative product'lar üreten testlerde görünmez; bu durumda div ile floor aynı sonucu verir. Aynı nedenle HotPDF WebP testleri tolerance yerine aynı dosyaların Pillow decode'larına karşı pixel-exact equality ister: gradient'ler, tek olmayan 100x37 boyutu, gerçek alpha channel taşıyan 40x40 görüntü ve düz 32x32 görüntü, her piksel bit bit karşılaştırılır. Tek adımlık bir kayma perceptual check'ten geçer, bitwise check'ten kalır
WebP desteği neyi bilerek reddeder?
HotPDF bir WebP dosyasının ilk VP8L chunk'ını decode eder, başka hiçbir şeyi değil. Lossy VP8 frame'leri, animation'lar ve eşleşen chunk'ı VP8L olmayan her container HPDFDecodeWebPLossless'tan False döner; AddImage bunu dosyanın adını belirten Failed to decode WebP image (lossless VP8L only) exception'ına çevirir. Bu bir oversight değil, bilerek konmuş bir sınırdır: yanlış formatlı dosya, çağıranın önceden dönüştürebileceği yerde başarısız olmalı, gri bir dikdörtgen üretmemelidir. Version field 0 olmalı, transform stack dört entry ile sınırlandırılmıştır ve her bounds violation EWebPDecode yükseltir; public entry point bunu düz bir False'a çevirir. Import sırasında decode etmek, açtığınız bir belgeden görüntüleri geri çekmenin ters yönüdür; bu işlem yüklenmiş PDF'den görüntüleri ve decode filter'larını çıkarma yolundan geçer. Ayrıca her görüntü decoder'ı sizin üretmediğiniz dosyalarla beslenen bir parser'dır: WebP asset'leri müşterilerden veya public internet'ten geliyorsa burada yapılan bounds check'leri tabandır, tavan değil; daha güçlü cevap image codec'lerini izole bir worker process'te çalıştırmaktır, böylece bozuk bir frame host'u da beraberinde düşüremez
Pratik sonuç, bir Delphi veya C++Builder uygulamasının artık WebP asset'lerini PNG'yi koyduğu gibi PDF'e koyabilmesidir: AddImageFromFile'a tek çağrı, ShowImage'a tek çağrı ve installer'a ekstra hiçbir şey yok. Etrafındaki image ve document pipeline'ın geri kalanını istiyorsanız HotPDF Delphi PDF component yazma, yükleme ve render taraflarını aynı unit set'ten kapsar