HotPDF 2.747.0 dekoder WebP-bilder med en VP8L-dekoder (WebP tapsfri) skrevet fra grunnen av i Object Pascal, slik at THotPDF.AddImageFromFile tar imot en .webp-sti direkte, uten en libwebp-DLL å levere og uten en hjelpeprosess å starte. Dekoderen implementerer RFC 9649 seksjon 3 i sin helhet: RIFF-beholdergjennomgangen, kanoniske prefikskoder, LZ77-bakoverreferanser, fargecachen og alle fire inverse transformasjoner. Lossy VP8-rammer avvises tydelig i stedet for å bli halvt dekodet
Utløseren var hverdagslig. Et designverktøy eksporterer hver ressurs som WebP fordi det er den moderne standarden, ressursene havner i en faktura- eller kataloggenerator som har spist PNG og JPEG i ti år, og plutselig avvises halvparten av inndataene. Den åpenbare løsningen er å binde libwebp og gå videre. Den åpenbare løsningen er også den som gjør en selvstendig VCL-komponent om til noe med en distribusjonshistorie
Hvorfor implementere VP8L i stedet for å binde libwebp?
HotPDF implementerer kodeken i Pascal fordi en Delphi-komponent som kundene kompilerer inn i sin egen kjørbare fil, ikke stille kan skaffe seg en runtime-DLL. En native avhengighet betyr en 32- og en 64-biters binærfil å følge opp, en versjon å låse, en kodesigneringskjede å forklare til den som kjører distribusjonen, og én fil til som antivirus på en låst terminal kan bestemme seg for at den ikke liker. For en komponent hvis viktigste salgsargument er at den legges inn i et prosjekt og fungerer, er det en reell kostnad, ikke en teoretisk. Den andre halvdelen av argumentet er at VP8L er liten: et format med prefikskoder, LZ77, fire inverse transformasjoner og et avstandskart med 120 nabolag, og hele dekoderen i HPDFWebP.pas er under 900 linjer Pascal. Inne i THotPDF.AddImage ligger WebP-grenen i den samme extension-dispatchen som allerede sender .jp2, .j2k, .jpt og .jpc gjennom JPEG 2000-stien, så røropplegget var allerede der, på samme sted som beskrevet i gjennomgangen av å legge JPEG 2000-bilder til PDF-er i Delphi. Callers som vil ha rå piksler i stedet for et PDF-bilde, kan gå rett til HPDFDecodeWebPLossless, som fyller en TWebPCardinalArray med $AARRGGBB-verdier i skannelinjerekkefølge
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp sendes til den innebygde VP8L-dekoderen, uten DLL
Idx := Pdf.AddImageFromFile('product-shot.webp', icFlate);
Pdf.CurrentPage.ShowImage(Idx, 50, 500, 240, 180, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Hvorfor leses en VP8L-bitstrøm i to retninger samtidig?
Fordi bitrekkefølgen i beholderen og bitrekkefølgen i prefikskoden er spesifisert uavhengig av hverandre, og VP8L velger motsatte konvensjoner for dem. RFC 9649 seksjon 3.2 sier tydelig at bitstrømmen leses med minst signifikante bit først: Leseren starter på bit 0 i en byte og går oppover. De kanoniske prefikskodene i denne strømmen kommer med mest signifikante bit først, roten i treet først, så dekodeløkken flytter akkumulatoren til venstre og OR-er inn hvert nye bit nederst. Leseren og kodevandringen går derfor i motsatt retning inne i samme løkke, noe som ser ut som en feil hver gang du leser det på nytt
function TWebPBitReader.ReadBit: Integer;
begin
if BytePos >= Length(Data) then
raise EWebPDecode.Create('WebP bitstream exhausted');
Result := (Data[BytePos] shr BitPos) and 1; // LSB først, RFC 9649 3.2
Inc(BitPos);
if BitPos = 8 then
begin
BitPos := 0;
Inc(BytePos);
end;
end;
// den kanoniske vandringen går andre veien: det første bitet fra strømmen
// er den mest signifikante biten i koden
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;
Tre RFC-detaljer som stille desynkroniserer strømmen
Tre semantikker i RFC 9649 blir sagt nøyaktig én gang, er lette å lese forbi, og hver av dem koster eller sparer ett bit, noe som er nok til å gjøre hver senere tabell til støy. Alle tre ble funnet i HotPDFs VP8L-dekoder, og alle tre gir samme symptom: Et bilde som ser plausibelt ut, men er feil overalt
- Et entropikodet bilde i en ikke-primær rolle skriver ikke noe meta-prefiksbit i det hele tatt. ABNF-en for
entropy-coded-imageinneholder ganske enkelt ikke elementet, så å lese ett desynkroniserer strømmen med ett bit. HotPDF senderAllowMeta = Falsefor selve entropibildet, for predictor- og fargetransformasjonsdataene og for fargeindekseringspaletten - En prefikskode med ett blad bruker null bit. RFC 9649 seksjon 3.7.2.1 sier det direkte, og den kanoniske vandringen ville gladelig lest ett bit og deretter ikke klart å plassere det, så
BuildHuffoppdager et totalsymbolantall på 1 og merker treetSingle, og dekoder dette ene symbolet uten å røre leseren - En cache_bits-verdi på 0 betyr at fargecachestørrelsen er 0, ikke
1 shl 0. Det praktiske skiftet gir 1, noe som gjør at det grønne alfabetet256 + 24 + CacheSizeblir 281 i stedet for 280, og hver prefikskodetabell som leses etterpå blir feiljustert
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 betyr virkelig ingen 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: bare det romlig kodede (ARGB-)bildet har meta-prefiksbiten;
// entropikodede roller skriver den aldri
if AllowMeta then
UseMeta := BR.ReadBit
else
UseMeta := 0;
// ...
ReadHuffCode(256 + 24 + CacheSize, Groups[I].Green); // 280, ikke 281
ReadHuffCode(256, Groups[I].Red);
ReadHuffCode(256, Groups[I].Blue);
ReadHuffCode(256, Groups[I].Alpha);
ReadHuffCode(40, Groups[I].Dist);
I fixture-et som ble brukt under oppstarten, dukket de tre opp ved bit 47, bit 81 og bit 89, i den rekkefølgen. Det er poenget med denne delen. Ingen av de tre kunngjorde seg som en off-by-one; hver presenterte seg som et bilde som ble dekodet til ferdig og så ut som statisk støy, og det eneste som skilte dem, var den nøyaktige bitposisjonen der strømmen sluttet å stemme med en referanse
Hva får du ut av differanser på bitposisjon?
Differanser på bitposisjon gjør et ubrukelig spørsmål om til et spørsmål på én linje: ikke hvorfor er dette bildet feil, men hvorfor avvek strømmen ved bit 81. Oppsettet er billig. Pillow skriver hvert .webp-fixture sammen med en .rgba-dump av sin egen dekoding av det samme bildet; en Pascal-probe og en liten Python-referansemodell logger en løpende bit-teller ved siden av hver lesing; den første posisjonen der de to loggene er uenige, er der feilen bor. Start med et fixture som gjør minst mulig: et flatt bilde på 32x32 som bare bruker stien med enkle koder. Få det grønt, og legg deretter til gradienter, uvanlige dimensjoner og alpha, ett fixture om gangen. Å gjette bitrekkefølgen i stedet er en måte å bruke en dag på
Det ærlige forbeholdet er at referansen også var feil. Python-modellen glemte å lese cache_bits, og transformasjonsløkken kjørte ikke til slutt, så noen avviksposisjoner skyldtes at referansedekoderen mistet synkroniseringen, ikke Pascal-versjonen. At en referanseimplementasjon er feil, gjør ikke implementasjonen som testes riktig, og ingen av sidene får fordelen av tvilen: Hvert avvik må avgjøres mot RFC-teksten. Hent også den teksten fra kilden. Søkesammendrag ødelegger ofte numeriske tabeller, og avstandskartet med 120 oppføringer, de 14 prediktormodusene og fargecache-multiplikatoren $1e35a7bd må alle transkriberes nøyaktig
Hvor avviker Pascal-heltallsdivisjon fra C?
VP8L-fargetransformasjonen er 3.5 fixed point med fortegnede deltaer, og det er her Pascal og C slutter å være enige. C skifter negative heltall aritmetisk, noe som runder ned; Pascal div trunkerer mot null. For ethvert negativt produkt er de to forskjellige med én, så den inverse fargetransformasjonen driver med ett kanaltrinn per piksel over hele bildet. HotPDF gjør derfor avrundingen ned eksplisitt i FloorDiv32 i stedet for å stole på div
// C skifter aritmetisk og runder ned for negative verdier; Pascal div trunkerer
// mot null, så det negative tilfellet trenger en eksplisitt korrigering
function FloorDiv32(V: Integer): Integer;
begin
Result := V div 32;
if (V < 0) and (V mod 32 <> 0) then
Dec(Result);
end;
// 3.5 fixed-point-delta mellom en transformasjonselementbyte og en
// fargekanalbyte, begge først utvidet med fortegn
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;
Denne typen feil er verdt å navngi fordi den er usynlig i enhver test hvis fixture tilfeldigvis produserer ikke-negative produkter, der div og gulv er enige. Det er også grunnen til at HotPDFs WebP-tester hevder pikseleksakt likhet mot Pillow-dekoding av de samme filene i stedet for en toleranse: gradienter, en uvanlig størrelse på 100x37, et bilde på 40x40 med en reell alfakanal og et flatt bilde på 32x32, hver piksel sammenlignes bit for bit. En forskyvning på ett trinn består en perseptuell kontroll og feiler en bitvis kontroll
Hva nekter WebP-støtten med vilje?
HotPDF dekoder den første VP8L-chunken i en WebP-fil og ingenting annet. Lossy VP8-rammer, animasjoner og enhver beholder der den matchende chunken ikke er VP8L, returnerer False fra HPDFDecodeWebPLossless, og AddImage gjør dette til et unntak som navngir filen: Failed to decode WebP image (lossless VP8L only). Det er en bevisst grense, ikke en forglemmelse: En fil i feil format skal feile der caller-en kan konvertere den på forhånd, i stedet for å produsere et grått rektangel. Versjonsfeltet må være 0, transformasjonsstacken er begrenset til fire oppføringer og ethvert brudd på grensene kaster EWebPDecode, som det offentlige entry point-et gjør om til en vanlig False. Dekoding ved import er også motsatt retning av å trekke bilder ut fra et dokument du åpnet, noe som går gjennom stien for innlastede bilder beskrevet i uthenting av bilder fra en innlastet PDF og dekodefiltrene deres. Og enhver bilde-dekoder er en parser som mates med filer du ikke har laget: Hvis WebP-ressursene kommer fra kunder eller det offentlige nettet, er grensekontrollene her gulvet, ikke taket, og det sterkere svaret er å kjøre bilde-kodeker i en isolert worker-prosess, slik at en feilformet ramme ikke kan ta med seg verten ned
Det praktiske resultatet er at en Delphi- eller C++Builder-applikasjon nå kan legge WebP-ressurser i en PDF på samme måte som PNG: ett kall til AddImageFromFile, ett kall til ShowImage, ingenting ekstra i installasjonsprogrammet. Hvis du vil ha resten av bilde- og dokumentpipelinen rundt dette, dekker HotPDF Delphi PDF-komponenten skrive-, laste- og renderingssidene fra det samme enhetssettet