HotPDF 2.747.0 dekodira WebP slike dekoderom VP8L (WebP bez gubitaka) napisanim od nule u Object Pascalu, pa THotPDF.AddImageFromFile izravno prihvaća putanju .webp, bez DLL-a libwebp koji treba isporučiti i bez pomoćnog procesa koji treba pokrenuti. Dekoder u cijelosti implementira RFC 9649 odjeljak 3: prolaz kroz RIFF spremnik, kanonske prefiksne kodove, LZ77 povratne reference, predmemoriju boja i sve četiri inverzne transformacije. Okviri VP8 s gubitkom odbijaju se glasno umjesto napola dekodirati
Okidač je bio banalan. Alat za dizajn izvozi svaki resurs kao WebP jer je to moderni zadani izbor, resursi završavaju u generatoru računa ili kataloga koji je desetljeće bez problema gutao PNG i JPEG, a onda se odjednom polovica ulaza odbija. Očito rješenje jest povezati libwebp i nastaviti. Očito rješenje ujedno je ono koje samostalnu VCL komponentu pretvara u nešto s pričom o isporuci
Zašto implementirati VP8L umjesto povezivanja s libwebp?
HotPDF implementira kodek u Pascalu jer Delphi komponenta koju korisnici prevode u vlastitu izvršnu datoteku ne može potajno dobiti runtime DLL. Izvorna ovisnost znači pratiti binarnu datoteku od 32 i od 64 bita, odrediti verziju, objasniti lanac potpisivanja koda osobi koja pokreće isporuku i dodati još jednu datoteku koju antivirusni program na zaključanom terminalu može odlučiti da ne voli. Za komponentu čija je glavna prodajna prednost da se ubaci u projekt i radi, to je stvarni trošak, a ne teorijski. Druga polovica argumenta jest da je VP8L malen: format prefiksnih kodova i LZ77-a s četiri inverzne transformacije i mapom udaljenosti susjedstva od 120 unosa, a cijeli dekoder u HPDFWebP.pas ima manje od 900 redaka Pascala. Unutar THotPDF.AddImage grana WebP sjedi u istom odabiru ekstenzije koji već vodi .jp2, .j2k, .jpt i .jpc kroz put JPEG 2000, pa je vodovod već postojao, na istom mjestu opisanom u vodiču o dodavanju JPEG 2000 slika u PDF-ove u Delphiju. Pozivatelji koji žele sirove piksele umjesto PDF slike mogu izravno pozvati HPDFDecodeWebPLossless, koji popunjava TWebPCardinalArray vrijednostima $AARRGGBB redoslijedom scanlineova
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp se šalje ugrađenom VP8L dekoderu, bez 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;
Zašto se VP8L bitni tok čita u dvama smjerovima odjednom?
Jer su redoslijed bitova spremnika i redoslijed bitova prefiksnog koda specificirani neovisno, a VP8L za njih bira suprotne konvencije. RFC 9649 odjeljak 3.2 jasno kaže da se bitni tok čita od najmanje značajnog bita: čitač počinje na bitu 0 bajta i ide prema gore. Kanonski prefiksni kodovi koji se nalaze u tom toku dolaze od najznačajnijeg bita prema najmanje značajnom, najprije korijen stabla, pa prolaz dekodiranja pomiče akumulator ulijevo i novi bit postavlja OR-om na dno. Čitanje i prolaz kroz kod zato idu u suprotnim smjerovima unutar iste petlje, što svaki put izgleda kao pogreška kada ponovno čitate kod
function TWebPBitReader.ReadBit: Integer;
begin
if BytePos >= Length(Data) then
raise EWebPDecode.Create('WebP bitstream exhausted');
Result := (Data[BytePos] shr BitPos) and 1; // prvo LSB, RFC 9649 3.2
Inc(BitPos);
if BitPos = 8 then
begin
BitPos := 0;
Inc(BytePos);
end;
end;
// kanonski prolaz ide drugim smjerom: prvi bit iz toka
// najznačajniji je bit koda
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 RFC detalja koji tiho desinkroniziraju tok
Tri semantike u RFC-u 9649 navedene su točno jednom, lako ih je preletjeti pogledom, a svaka potroši ili uštedi jedan bit, što je dovoljno da svaka kasnija tablica postane šum. Sve tri pronađene su u HotPDF VP8L dekoderu i sve tri daju isti simptom: sliku koja izgleda vjerodostojno, ali je svugdje pogrešna
- Entropy-kodirana slika u neprimarnoj ulozi uopće ne zapisuje meta-prefiksni bit. ABNF za
entropy-coded-imagejednostavno ne sadrži tu stavku, pa čitanje jednog bita desinkronizira tok za bit. HotPDF predajeAllowMeta = Falsesamoj entropy slici, podacima prediktorske i kolor-transformacije te paleti indeksiranja boja - Prefiksni kod s jednim listom troši nula bitova. RFC 9649 odjeljak 3.7.2.1 to kaže izravno, a kanonski bi prolaz rado pročitao bit i zatim ga ne bi smjestio, pa
BuildHuffotkriva ukupan broj simbola 1 i stablo označava sSingle, dekodirajući taj jedan simbol bez dodirivanja čitača - Vrijednost cache_bits 0 znači da je veličina predmemorije boja 0, a ne
1 shl 0. Praktični pomak daje 1, zbog čega zeleni alfabet256 + 24 + CacheSizeispadne 281 umjesto 280, a svako sljedeće čitanje tablice prefiksnih kodova bude neporavnato
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 doista znači ništa
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 prostorno kodirana (ARGB) slika nosi
// meta-prefiksni bit; entropy-kodirane uloge ga nikad ne zapisuju
if AllowMeta then
UseMeta := BR.ReadBit
else
UseMeta := 0;
// Nastavak dekodiranja
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 testnoj datoteci korištenoj tijekom uvođenja tri su se pojavila na bitu 47, bitu 81 i bitu 89, tim redoslijedom. To suština je ovog odjeljka. Nijedno se nije najavilo kao pomak za jedan; svako se predstavilo kao slika koja se dekodira do kraja i izgleda kao statika, a jedino što ih je razdvajalo bio je točan položaj bita na kojem se tok prestao slagati s referencom
Što dobivate usporedbom razlika po položaju bita?
Usporedba razlika po položaju bita beskorisno pitanje pretvara u pitanje od jednog retka: ne zašto je ova slika pogrešna, nego zašto je tok odstupio na bitu 81. Postavljanje je jeftino. Pillow zapisuje svaku testnu datoteku .webp i .rgba ispis vlastitog dekodiranja iste slike; Pascalova sonda i mali Pythonov referentni model uz svako čitanje zapisuju rastući brojač bitova; prvo mjesto na kojem se dva zapisa ne slažu mjesto je pogreške. Počnite s testom koji vježba što je moguće manje: ravna slika 32x32 koja prolazi samo putom jednostavnog koda. Neka to proradi, pa dodajte gradijente, neparne dimenzije i alfa kanal, jednu datoteku po jednu. Pogađanje redoslijeda bitova umjesto toga način je da potrošite dan
Poštena napomena jest da je i referenca bila pogrešna. Pythonov model zaboravio je pročitati cache_bits, a njegova petlja transformacije nije išla do kraja, pa su neke točke odstupanja bile gubitak sinkronizacije referentnog dekodera, a ne Pascalova. Pogrešna referentna implementacija ne čini implementaciju pod testom ispravnom, a nijedna strana nema pravo na pretpostavku nevinosti: svako odstupanje mora se presuditi prema tekstu RFC-a. Taj tekst dohvatite iz izvora. Sažeci pretrage rutinski pogrešno prenesu brojčane tablice, a mapa udaljenosti sa 120 unosa, 14 načina prediktora i množitelj predmemorije boja $1e35a7bd moraju se svi prepisati točno
Gdje se cjelobrojno dijeljenje Pascala razlikuje od C-a
Transformacija boje VP8L je fiksna točka 3.5 s predznakovnim delta vrijednostima i upravo tu se Pascal i C prestaju slagati. C aritmetički pomiče negativne cijele brojeve, što zaokružuje prema dolje; Pascalov div odsijeca prema nuli. Za svaki negativni umnožak razlikuju se za jedan, pa inverzna transformacija boje kroz cijelu sliku odmiče za jedan korak kanala po pikselu. Zato HotPDF pod u FloorDiv32 izričito provodi zaokruživanje prema dolje umjesto oslanjanja na div
// C aritmetički pomiče i zaokružuje negativne vrijednosti prema dolje; Pascalov div odsijeca
// prema nuli, pa negativnom slučaju treba izričita korekcija
function FloorDiv32(V: Integer): Integer;
begin
Result := V div 32;
if (V < 0) and (V mod 32 <> 0) then
Dec(Result);
end;
// delta fiksne točke 3.5 između bajta elementa transformacije i bajta
// kanala boje, pri čemu se oba najprije proširuju sa znakom
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;
Ovu je vrstu nedostatka vrijedno imenovati jer je nevidljiva svakom testu čije testne datoteke slučajno proizvode nenegativne umnoške, kod kojih se div i zaokruživanje prema dolje slažu. To je ujedno razlog zbog kojeg HotPDF WebP testovi zahtijevaju jednakost piksela s dekodiranjem Pythona Pillow istih datoteka, a ne toleranciju: gradijenti, neparna veličina 100x37, slika 40x40 sa stvarnim alfa kanalom i ravna slika 32x32, svaki piksel uspoređen bit po bit. Pomak za jedan prolazi perceptivnu provjeru, a pada na bitnoj
Što WebP podrška namjerno odbija
HotPDF dekodira prvi VP8L blok WebP datoteke i ništa drugo. Okviri VP8 s gubitkom, animacije i svaki spremnik čiji odgovarajući blok nije VP8L vraćaju False iz HPDFDecodeWebPLossless, a AddImage to pretvara u iznimku koja imenuje datoteku: Failed to decode WebP image (lossless VP8L only). To je namjerna granica, a ne previd: datoteka pogrešnog formata treba zakazati ondje gdje je pozivatelj može unaprijed pretvoriti, umjesto da proizvede sivi pravokutnik. Polje verzije mora biti 0, stog transformacija ograničen je na četiri unosa, a svako prekoračenje granice podiže EWebPDecode, koji javna ulazna točka pretvara u obično False. Dekodiranje pri uvozu također je suprotan smjer od izvlačenja slika iz dokumenta koji ste otvorili, što prolazi kroz put učitane slike opisan u članku o izdvajanju slika iz učitanog PDF-a i njihovih filtara dekodiranja. Svaki dekoder slika usto je parser kojem se predaju datoteke koje niste stvorili: ako WebP resursi dolaze od korisnika ili s javnog interneta, provjere granica ovdje su pod, a ne strop, a snažniji je odgovor pokretanje slikovnih kodeka u izoliranom radnom procesu kako neispravan okvir ne bi povukao domaćina sa sobom
Praktični je rezultat da Delphi ili C++Builder aplikacija sada može staviti WebP resurse u PDF na isti način kao PNG: jedan poziv AddImageFromFile, jedan poziv ShowImage, ništa dodatno u instalacijskom programu. Ako želite ostatak slikovnog i dokumentnog cjevovoda koji ga okružuje, HotPDF Delphi PDF component pokriva strane pisanja, učitavanja i iscrtavanja iz istog skupa jedinica