HotPDF 2.747.0 mendekode image WebP dengan decoder VP8L (WebP lossless) yang ditulis dari awal dalam Object Pascal, sehingga THotPDF.AddImageFromFile menerima path .webp secara langsung, tanpa libwebp DLL untuk dikirim dan tanpa helper process untuk dijalankan. Decoder mengimplementasikan RFC 9649 section 3 secara penuh: penelusuran RIFF container, canonical prefix code, backward reference LZ77, color cache, dan keempat inverse transform. Frame VP8 lossy ditolak secara jelas, bukan didekode setengah-setengah
Pemicunya sederhana. Design tool mengekspor setiap asset sebagai WebP karena itu default modern, asset masuk ke invoice atau catalog generator yang selama satu dekade telah menerima PNG dan JPEG, lalu tiba-tiba separuh input ditolak. Perbaikan yang terlihat jelas adalah bind libwebp dan selesai. Perbaikan yang terlihat jelas itu juga mengubah self-contained VCL component menjadi sesuatu yang memiliki deployment story
Mengapa mengimplementasikan VP8L alih-alih bind libwebp?
HotPDF mengimplementasikan codec dalam Pascal karena komponen Delphi yang dikompilasi customer ke executable mereka sendiri tidak boleh diam-diam memperoleh runtime DLL. Native dependency berarti harus melacak binary 32-bit dan 64-bit, menetapkan version, menjelaskan code-signing chain kepada pihak yang menjalankan deployment, serta menambah satu file yang mungkin tidak disukai antivirus pada terminal yang dikunci. Untuk component yang nilai utamanya adalah dapat dimasukkan ke proyek lalu langsung bekerja, itu biaya nyata, bukan teori. Separuh argumen lainnya adalah VP8L berukuran kecil: format prefix-code plus LZ77 dengan empat inverse transform dan 120-entry neighborhood distance map, sementara seluruh decoder dalam HPDFWebP.pas kurang dari 900 baris Pascal. Di dalam THotPDF.AddImage, branch WebP berada pada extension dispatch yang sama yang sudah mengarahkan .jp2, .j2k, .jpt, dan .jpc melalui JPEG 2000 path, sehingga plumbing-nya sudah tersedia, di tempat yang sama seperti yang dijelaskan dalam walkthrough menambahkan image JPEG 2000 ke PDF di Delphi. Caller yang menginginkan raw pixel, bukan PDF image, dapat langsung memakai HPDFDecodeWebPLossless, yang mengisi TWebPCardinalArray berisi value $AARRGGBB dalam urutan scan-line
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp diarahkan ke built-in VP8L decoder, tanpa DLL
Idx := Pdf.AddImageFromFile('product-shot.webp', icFlate);
Pdf.CurrentPage.ShowImage(Idx, 50, 500, 240, 180, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Mengapa bitstream VP8L dibaca dalam dua arah sekaligus?
Karena bit order container dan bit order prefix code ditentukan secara independen, dan VP8L memilih konvensi yang berlawanan untuk keduanya. RFC 9649 section 3.2 menyatakan dengan jelas bahwa bitstream dibaca least significant bit first: reader mulai pada bit 0 sebuah byte lalu bergerak naik. Canonical prefix code yang dibawa stream hadir most significant bit first, dari root tree lebih dulu, sehingga decode walk menggeser accumulator ke kiri dan melakukan OR setiap bit baru di bagian bawah. Reader dan code walk karena itu bergerak ke arah berlawanan dalam loop yang sama, sesuatu yang terlihat seperti bug setiap kali Anda membacanya ulang
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 walk bergerak ke arah lain: bit pertama dari stream
// adalah most significant bit dari code
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;
Tiga detail RFC yang diam-diam membuat stream kehilangan sinkronisasi
Tiga semantics dalam RFC 9649 dinyatakan hanya sekali, mudah terlewat saat dibaca, dan masing-masing memakan atau menghemat satu bit, yang cukup untuk mengubah semua table setelahnya menjadi noise. Ketiganya ditemukan dalam decoder VP8L HotPDF, dan ketiganya menghasilkan gejala yang sama: image tampak masuk akal tetapi salah di mana-mana
- Image entropy-coded dalam non-primary role tidak menulis meta-prefix bit sama sekali. ABNF untuk
entropy-coded-imagememang tidak memuat item tersebut, sehingga membacanya akan membuat stream desync satu bit. HotPDF meneruskanAllowMeta = Falseuntuk entropy image itu sendiri, data predictor dan color transform, serta palette color-indexing - Prefix code dengan satu leaf menggunakan nol bit. RFC 9649 section 3.7.2.1 menyatakannya secara langsung, dan canonical walk akan dengan senang hati membaca satu bit lalu gagal menempatkannya, sehingga
BuildHuffmendeteksi total symbol 1 dan menandai tree sebagaiSingle, lalu mendekode satu symbol itu tanpa menyentuh reader - Nilai cache_bits 0 berarti ukuran color cache 0, bukan
1 shl 0. Shift yang mudah memberi 1, sehingga green alphabet256 + 24 + CacheSizemenjadi 281, bukan 280, dan setiap prefix code table yang dibaca setelahnya menjadi misaligned
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 benar-benar berarti tidak ada cache
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: hanya image spatially coded (ARGB) yang membawa
// meta prefix bit; role entropy-coded tidak pernah menulisnya
if AllowMeta then
UseMeta := BR.ReadBit
else
UseMeta := 0;
// ...
ReadHuffCode(256 + 24 + CacheSize, Groups[I].Green); // 280, bukan 281
ReadHuffCode(256, Groups[I].Red);
ReadHuffCode(256, Groups[I].Blue);
ReadHuffCode(256, Groups[I].Alpha);
ReadHuffCode(40, Groups[I].Dist);
Pada fixture yang dipakai selama bring-up, ketiganya muncul pada bit 47, bit 81, dan bit 89, dalam urutan tersebut. Itulah inti bagian ini. Tidak satu pun menyatakan dirinya sebagai off-by-one; masing-masing tampil sebagai image yang selesai didekode dan terlihat seperti static, dan satu-satunya hal yang membedakannya adalah posisi bit tepat ketika stream berhenti cocok dengan reference
Apa manfaat bit-position diffing?
Bit-position diffing mengubah pertanyaan yang tidak berguna menjadi pertanyaan satu baris: bukan mengapa gambar ini salah, melainkan mengapa stream menyimpang pada bit 81. Setup-nya murah. Pillow menulis setiap fixture .webp beserta dump .rgba hasil decode image yang sama; Pascal probe dan Python reference model kecil sama-sama mencatat running bit counter di samping setiap read; posisi pertama ketika kedua log berbeda adalah tempat bug berada. Mulailah dengan fixture yang menguji sesedikit mungkin: image datar 32x32 yang hanya mengambil simple-code path. Setelah itu benar, tambahkan gradient, dimensi ganjil, dan alpha satu fixture demi satu. Menebak bit order adalah cara menghabiskan satu hari
Caveat jujurnya adalah reference juga sempat salah. Python model lupa membaca cache_bits dan transform loop-nya tidak berjalan sampai selesai, sehingga beberapa divergence point adalah reference decoder yang kehilangan sync, bukan Pascal decoder. Reference implementation yang salah tidak membuat implementation yang diuji otomatis benar, dan tidak ada pihak yang mendapat benefit of the doubt: setiap divergence harus diputuskan berdasarkan teks RFC. Ambil teks itu dari source juga. Search summary sering merusak numeric table, dan 120-entry distance map, 14 predictor mode, serta color cache multiplier $1e35a7bd semuanya harus ditranskripsi dengan tepat
Di mana integer division Pascal berbeda dari C?
Color transform VP8L menggunakan signed delta fixed point 3.5, dan di situlah Pascal dan C berhenti sepakat. C menggeser integer negatif secara aritmetis, yang berarti floor; Pascal div melakukan truncation menuju nol. Untuk setiap product negatif, keduanya berbeda satu, sehingga inverse color transform bergeser satu channel step per pixel di seluruh image. Karena itu HotPDF melakukan floor secara eksplisit dalam FloorDiv32, bukan bergantung pada div
// C melakukan arithmetic shift dan floor pada nilai negatif; Pascal div melakukan truncation
// menuju nol, jadi kasus negatif membutuhkan koreksi eksplisit
function FloorDiv32(V: Integer): Integer;
begin
Result := V div 32;
if (V < 0) and (V mod 32 <> 0) then
Dec(Result);
end;
// delta fixed-point 3.5 antara byte elemen transform dan byte
// channel warna, keduanya lebih dulu di-sign-extend
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;
Kelas defect ini layak diberi nama karena tidak terlihat pada test yang fixture-nya kebetulan menghasilkan product non-negatif, saat div dan floor sama. Ini juga alasan test HotPDF WebP menegaskan equality pixel-exact terhadap decode Pillow dari file yang sama, bukan tolerance: gradient, ukuran ganjil 100x37, image 40x40 dengan alpha channel nyata, dan image datar 32x32, setiap pixel dibandingkan bit demi bit. Drift satu langkah lolos dari perceptual check tetapi gagal pada bitwise check
Apa yang sengaja ditolak oleh dukungan WebP?
HotPDF mendekode chunk VP8L pertama dari file WebP dan tidak melakukan hal lain. Lossy VP8 frame, animation, dan container yang matching chunk-nya bukan VP8L mengembalikan False dari HPDFDecodeWebPLossless, dan AddImage mengubahnya menjadi exception yang menyebut nama file: Failed to decode WebP image (lossless VP8L only). Ini batasan yang disengaja, bukan kelalaian: file dengan format salah seharusnya gagal di tempat caller dapat melakukan pre-convert, bukan menghasilkan rectangle abu-abu. Version field harus 0, transform stack dibatasi empat entry, dan setiap bounds violation mengangkat EWebPDecode, yang oleh public entry point diubah menjadi False biasa. Decoding saat import juga merupakan arah yang berlawanan dari mengambil kembali image dari dokumen yang Anda buka, yang berjalan melalui loaded-image path yang dijelaskan dalam mengekstrak image dari PDF loaded dan decode filter-nya. Dan setiap image decoder adalah parser yang diberi file yang tidak Anda buat: jika asset WebP datang dari customer atau internet publik, bounds check di sini adalah lantai, bukan plafon, dan jawaban yang lebih kuat adalah menjalankan image codec dalam isolated worker process agar malformed frame tidak menjatuhkan host bersamanya
Hasil praktisnya adalah aplikasi Delphi atau C++Builder kini dapat memasukkan asset WebP ke PDF dengan cara yang sama seperti PNG: satu pemanggilan AddImageFromFile, satu pemanggilan ShowImage, dan tidak ada tambahan di installer. Jika Anda menginginkan pipeline image dan document lain di sekitarnya, HotPDF Delphi PDF component mencakup sisi writing, loading, dan rendering dari set unit yang sama