HotPDF 2.747.0 dekodira slike WebP z dekodirnikom VP8L (WebP brez izgub), napisanim na novo v Object Pascalu, zato THotPDF.AddImageFromFile pot .webp sprejme neposredno, brez dostave DLL-a libwebp in brez zagona pomožnega procesa. Dekodirnik v celoti izvaja RFC 9649, razdelek 3: sprehod vsebnika RIFF, kanonične predponske kode, povratne sklice LZ77, barvni predpomnilnik in vse štiri inverzne transformacije. Okvirji VP8 z izgubami se glasno zavrnejo, namesto da bi se napol dekodirali
Sprožilec je bil vsakdanji. Oblikovalsko orodje vsak vir izvozi kot WebP, ker je to sodobna privzeta možnost, viri končajo v generatorju računov ali katalogov, ki že desetletje brez težav sprejema PNG in JPEG, nato pa je nenadoma polovica vhodov zavrnjena. Očiten popravek je povezati libwebp in nadaljevati. Očiten popravek je tudi tisti, ki iz samostojne komponente VCL naredi nekaj z zgodbo o nameščanju
Zakaj implementirati VP8L namesto povezati libwebp?
HotPDF kodek implementira v Pascalu, ker komponenta Delphi, ki jo stranke prevedejo v lastno izvršno datoteko, ne more tiho dobiti izvajalnega DLL-a. Izvorna odvisnost pomeni binarno datoteko za 32-bitni in 64-bitni sistem, različico za zaklepanje, verigo podpisovanja kode, ki jo je treba razložiti izvajalcu namestitve, in še eno datoteko, ki se je protivirusna programska oprema na zaklenjenem terminalu lahko odloči ne marati. Za komponento, katere glavna prodajna prednost je, da jo dodaš v projekt in deluje, je to resničen strošek, ne teoretičen. Druga polovica argumenta je, da je VP8L majhen: format s predponskimi kodami in LZ77, s štirimi inverznimi transformacijami in zemljevidom razdalj s 120 vnosi, celoten dekodirnik v HPDFWebP.pas pa ima manj kot 900 vrstic Pascala. Znotraj THotPDF.AddImage veja WebP sedi v isti izbiri po priponi, ki .jp2, .j2k, .jpt in .jpc že pošilja skozi pot JPEG 2000, zato je bila napeljava že tam, na istem mestu, ki je opisano v vodiču o dodajanju slik JPEG 2000 v PDF-je v Delphiju. Klicatelji, ki želijo surove piksle namesto slike PDF, lahko gredo neposredno v HPDFDecodeWebPLossless, ki zapolni TWebPCardinalArray vrednosti $AARRGGBB v vrstnem redu vrstic skeniranja
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp se pošlje v vgrajeni dekodirnik VP8L, brez DLL-a
Idx := Pdf.AddImageFromFile('product-shot.webp', icFlate);
Pdf.CurrentPage.ShowImage(Idx, 50, 500, 240, 180, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Zakaj se bitni tok VP8L bere v dveh smereh hkrati?
Ker sta vrstni red bitov vsebnika in vrstni red bitov predponske kode določena neodvisno, VP8L pa za njiju izbere nasprotni konvenciji. RFC 9649, razdelek 3.2, jasno določa, da se bitni tok bere od najmanj pomembnega bita naprej: bralnik začne pri bitu 0 bajta in hodi navzgor. Kanonične predponske kode, ki so v tem toku, prispejo od najpomembnejšega bita naprej, od korena drevesa, zato dekodirni sprehod akumulator pomakne v levo in vsak nov bit z OR doda na dno. Bralnik in sprehod kode torej v isti zanki tečeta v nasprotnih smereh, kar je videti kot napaka vsakič, ko kodo znova bereš
function TWebPBitReader.ReadBit: Integer;
begin
if BytePos >= Length(Data) then
raise EWebPDecode.Create('WebP bitstream exhausted');
Result := (Data[BytePos] shr BitPos) and 1; // najprej LSB, RFC 9649 3.2
Inc(BitPos);
if BitPos = 8 then
begin
BitPos := 0;
Inc(BytePos);
end;
end;
// kanonični sprehod teče v drugo smer: prvi bit iz toka je
// najpomembnejši bit kode
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;
Tri podrobnosti RFC, ki tiho desinhronizirajo tok
Tri semantike v RFC 9649 so zapisane natanko enkrat, zlahka jih spregledamo in vsaka stane ali prihrani en bit, kar je dovolj, da vsaka poznejša tabela postane šum. Vse tri so bile odkrite v dekodirniku HotPDF VP8L in vse tri povzročijo isti simptom: sliko, ki je videti verjetna, vendar je povsod napačna
- Entropijsko kodirana slika v neprimarni vlogi ne zapiše nobenega metapredponskega bita. ABNF za
entropy-coded-imagetega elementa preprosto ne vsebuje, zato branje enega toka desinhronizira za bit. HotPDF za samo entropijsko sliko, za podatke transformacije napovedovalnika in barvno transformacijo ter za paleto barvnega indeksiranja posredujeAllowMeta = False - Predponska koda z enim listom porabi nič bitov. RFC 9649, razdelek 3.7.2.1, to pove neposredno, kanonični sprehod pa bi z veseljem prebral bit in ga nato ne bi znal postaviti, zato
BuildHuffzazna skupno število simbolov 1 in drevo označi kotSingle, ta en simbol pa dekodira brez dotika bralnika - Vrednost cache_bits 0 pomeni, da je velikost barvnega predpomnilnika 0, ne
1 shl 0. Priročen pomik da 1, zaradi česar abeceda zelene barve256 + 24 + CacheSizepride 281 namesto 280 in je vsaka tabela predponskih kod, prebrana za tem, napačno poravnana
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 res pomeni nič
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: samo prostorsko kodirana slika (ARGB) nosi
// metapredponski bit; entropijsko kodirane vloge ga nikoli ne zapišejo
if AllowMeta then
UseMeta := BR.ReadBit
else
UseMeta := 0;
// ...
ReadHuffCode(256 + 24 + CacheSize, Groups[I].Green); // 280, ne 281
ReadHuffCode(256, Groups[I].Red);
ReadHuffCode(256, Groups[I].Blue);
ReadHuffCode(256, Groups[I].Alpha);
ReadHuffCode(40, Groups[I].Dist);
Na testnem primeru, uporabljenem med zagonom, so se tri pokazale pri bitu 47, bitu 81 in bitu 89, v tem vrstnem redu. To je namen tega odseka. Nobena od treh se ni javila kot napaka za ena; vsaka je bila videti kot slika, ki se je dekodirala do konca in je bila podobna šumu, ločila pa jih je samo natančna bitna pozicija, pri kateri se tok ni več ujemal z referenco
Kaj pridobiš z razlikovanjem po položaju bita?
Razlikovanje po položaju bita neuporabno vprašanje spremeni v vprašanje ene vrstice: ne »zakaj je ta slika napačna«, temveč »zakaj se je tok razšel pri bitu 81«. Priprava je poceni. Pillow zapiše vsak testni primer .webp skupaj z izpisom .rgba, ki ga iz iste slike ustvari sam; Pascalova sonda in majhen referenčni model v Pythonu ob vsakem branju beležita tekoči števec bitov; prvi položaj, kjer se dnevnika ne ujemata, je mesto napake. Začnite s primerom, ki izvaja čim manj: ravna slika 32x32, ki potrebuje samo pot preproste kode. Ko je ta pravilna, dodajajte prelive, nenavadne dimenzije in alfo, po en primer naenkrat. Ugibanje vrstnega reda bitov je način, kako porabiti cel dan
Poštena pripomba je, da je bila napačna tudi referenca. Pythonov model je pozabil prebrati cache_bits, njegova zanka transformacij pa se ni izvedla do konca, zato so bile nekatere točke razhoda referenčni dekodirnik, ki je izgubil sinhronizacijo, ne Pascalov dekodirnik. Napačna referenčna implementacija ne pomeni, da je implementacija v preizkusu pravilna, in nobena stran nima koristi od dvoma: vsak razhod je treba razsoditi glede na besedilo RFC. Tudi to besedilo pridobite iz vira. Povzetki iskanja rutinsko popačijo številske tabele, zemljevid razdalj s 120 vnosi, 14 načinov napovedovalnika in množilnik barvnega predpomnilnika $1e35a7bd pa morajo biti prepisani natanko
Kje se celoštevilsko deljenje Pascala razlikuje od C
Barvna transformacija VP8L je fiksna vejica 3.5 s predznačenimi odmiki in prav tu se Pascal in C nehata strinjati. C negativna cela števila pomika aritmetično, kar zaokroži navzdol; Pascalov div odreže proti ničli. Pri vsakem negativnem produktu se razlikujeta za ena, zato se inverzna barvna transformacija čez celotno sliko premakne za en kanalni korak na piksel. HotPDF zato tla izrecno izvede v FloorDiv32, namesto da bi se zanesel na div
// C aritmetično pomika in pri negativnih vrednostih zaokroži navzdol; Pascalov div odreže
// proti ničli, zato negativni primer potrebuje izrecni popravek
function FloorDiv32(V: Integer): Integer;
begin
Result := V div 32;
if (V < 0) and (V mod 32 <> 0) then
Dec(Result);
end;
// fiksni odmik 3.5 med bajtom elementa transformacije in bajtom
// barvnega kanala, pri čemer sta oba najprej razširjena s predznakom
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;
Ta razred napake je vredno poimenovati, ker je neviden pri vsakem testu, katerega primeri po naključju ustvarijo ne-negativne produkte, pri katerih se div in tla ujemata. Zato tudi testi HotPDF WebP zahtevajo natančno enakost pikslov glede na dekodiranja Pillow istih datotek, ne tolerance: prelivi, nenavadna velikost 100x37, slika 40x40 z resničnim kanalom alfa in ravna slika 32x32, vsak piksel se primerja bit za bitom. Premik za en korak prestane zaznavni preizkus in pade pri bitnem
Česa podpora WebP namerno ne sprejema
HotPDF dekodira prvi kos VP8L datoteke WebP in ničesar drugega. Okvirji VP8 z izgubami, animacije in vsak vsebnik, katerega ujemajoči se kos ni VP8L, vrnejo False iz HPDFDecodeWebPLossless, AddImage pa to spremeni v izjemo, ki poimenuje datoteko: Failed to decode WebP image (lossless VP8L only). To je namerna meja, ne spregled: datoteka napačnega formata mora odpovedati tam, kjer jo klicatelj lahko predhodno pretvori, ne pa ustvariti sivega pravokotnika. Polje različice mora biti 0, sklad transformacij je omejen na štiri vnose, vsaka kršitev meja pa sproži EWebPDecode, ki jo javna vstopna točka spremeni v navaden False. Dekodiranje pri uvozu je tudi nasprotna smer od izvlečenja slik iz odprtega dokumenta, ki poteka po poti naloženih slik, opisani pri izločanju slik iz naloženega PDF-ja in njihovih dekodirnih filtrov. Poleg tega je vsak dekodirnik slik razčlenjevalnik, ki ga hranijo datoteke, ki jih niste ustvarili: če viri WebP pridejo od strank ali z javnega interneta, so tukajšnja preverjanja meja tla, ne strop, močnejši odgovor pa je izvajanje slikovnih kodekov v izoliranem delovnem procesu, da pokvarjen okvir s seboj ne sesuje gostitelja
Praktični rezultat je, da lahko aplikacija Delphi ali C++Builder zdaj v PDF vstavi vire WebP enako kot PNG: en klic AddImageFromFile, en klic ShowImage, ničesar dodatnega v namestitvenem programu. Če želite preostali slikovni in dokumentni cevovod okoli tega, komponenta HotPDF Delphi PDF pokriva pisanje, nalaganje in izrisovanje iz istega nabora enot