HotPDF 2.747.0 avkodar WebP-bilder med en VP8L-avkodare (WebP lossless) som skrivits från grunden i Object Pascal, så THotPDF.AddImageFromFile accepterar en .webp-sökväg direkt, utan någon libwebp-DLL att leverera och utan någon hjälpprocess att starta. Avkodaren implementerar RFC 9649 avsnitt 3 fullt ut: RIFF-behållarens genomgång, kanoniska prefixkoder, LZ77-bakåtreferenser, färgcachen och alla fyra inversa transformeringar. Förlustgivande VP8-ramar avvisas tydligt i stället för att halvt avkodas
Utlösaren var vardaglig. Ett designverktyg exporterar varje resurs som WebP eftersom det är det moderna standardvalet, resurserna hamnar i en faktura- eller kataloggenerator som utan problem har ätit PNG och JPEG i ett decennium, och plötsligt avvisas hälften av indata. Den uppenbara lösningen är att binda libwebp och gå vidare. Den uppenbara lösningen är också den som förvandlar en fristående VCL-komponent till något med en distributionsberättelse
Varför implementera VP8L i stället för att binda libwebp?
HotPDF implementerar codecen i Pascal eftersom en Delphi-komponent som kunder kompilerar in i sin egen körbara fil inte i tysthet kan skaffa sig en runtime-DLL. Ett inbyggt beroende betyder en 32-bitars och en 64-bitars binär att hålla reda på, en version att låsa, en kodsigneringskedja att förklara för den som kör distributionen och ytterligare en fil som antivirusprogram på en låst terminal kan bestämma sig för att ogilla. För en komponent vars främsta försäljningsargument är att den läggs in i ett projekt och fungerar är det en verklig kostnad, inte en teoretisk. Den andra halvan av argumentet är att VP8L är liten: ett prefixkod- och LZ77-format med fyra inversa transformeringar och en avståndskarta med 120 grannar, och hela avkodaren i HPDFWebP.pas är under 900 Pascal-rader. Inne i THotPDF.AddImage ligger WebP-grenen i samma tilläggsdispatch som redan skickar .jp2, .j2k, .jpt och .jpc genom JPEG 2000-vägen, så rördragningen fanns redan där, på samma plats som beskrivs i genomgången av att lägga till JPEG 2000-bilder i PDF-filer i Delphi. Anropare som vill ha råa pixlar i stället för en PDF-bild kan gå direkt till HPDFDecodeWebPLossless, som fyller en TWebPCardinalArray med $AARRGGBB-värden i skanningsradsordning
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp skickas till den inbyggda VP8L-avkodaren, ingen DLL behövs
Idx := Pdf.AddImageFromFile('product-shot.webp', icFlate);
Pdf.CurrentPage.ShowImage(Idx, 50, 500, 240, 180, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Varför läses en VP8L-bitström i två riktningar samtidigt?
För att behållarens bitordning och prefixkodens bitordning anges oberoende av varandra, och VP8L väljer motsatta konventioner för dem. RFC 9649 avsnitt 3.2 säger uttryckligen att bitströmmen läses med minst signifikant bit först: läsaren börjar vid bit 0 i en byte och går uppåt. De kanoniska prefixkoderna i strömmen kommer mest signifikant bit först, trädets rot först, så avkodningsvandringen skiftar ackumulatorn åt vänster och OR:ar in varje ny bit längst ned. Läsaren och kodvandringen går alltså i motsatta riktningar i samma loop, vilket ser ut som ett fel varje gång man läser om den
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 kanoniska vandringen går åt andra hållet: den första biten från strömmen
// är kodens mest signifikanta bit
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 tyst tappar synkroniseringen
Tre semantiker i RFC 9649 anges exakt en gång, är lätta att läsa förbi och kostar eller sparar varsin bit, vilket räcker för att göra varje senare tabell till brus. Alla tre hittades i HotPDF:s VP8L-avkodare och alla tre ger samma symptom: en bild som ser plausibel ut men är fel överallt
- En entropikodad bild i en icke-primär roll skriver ingen metaprefixbit alls. ABNF:n för
entropy-coded-imageinnehåller helt enkelt inte posten, så att läsa en desynkroniserar strömmen med en bit. HotPDF skickarAllowMeta = Falseför själva entropibilden, för prediktor- och färgtransformdata och för färgindexeringspaletten - Ett prefixkodträd med ett enda löv förbrukar noll bitar. RFC 9649 avsnitt 3.7.2.1 säger det direkt, och den kanoniska vandringen skulle gärna läsa en bit och sedan misslyckas med att placera den, så
BuildHuffupptäcker ett totalt symbolsantal på 1 och markerar trädet somSingle, och avkodar den enda symbolen utan att röra läsaren - Ett värde på 0 för cache_bits betyder att färgcachens storlek är 0, inte
1 shl 0. Den bekväma skiftningen ger 1, vilket gör det gröna alfabetet256 + 24 + CacheSizetill 281 i stället för 280, och varje prefixkodtabell som läses efter det blir feljusterad
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 betyder verkligen inget
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: bara den rumsligt kodade (ARGB-)bilden bär
// metaprefixbiten; entropikodade roller skriver den aldrig
if AllowMeta then
UseMeta := BR.ReadBit
else
UseMeta := 0;
// ...
ReadHuffCode(256 + 24 + CacheSize, Groups[I].Green); // 280, inte 281
ReadHuffCode(256, Groups[I].Red);
ReadHuffCode(256, Groups[I].Blue);
ReadHuffCode(256, Groups[I].Alpha);
ReadHuffCode(40, Groups[I].Dist);
I det fixture som användes under uppstarten visade sig de tre vid bit 47, bit 81 och bit 89, i den ordningen. Det är poängen med avsnittet. Ingen av de tre meddelade sig som ett fel med ett steg; var och en presenterade sig som en bild som avkodades till slut och såg ut som brus, och det enda som skiljde dem åt var den exakta bitposition där strömmen slutade stämma med en referens
Vad ger diffning på bitpositioner?
Diffning på bitpositioner omvandlar en värdelös fråga till en fråga på en rad: inte varför är bilden fel utan varför avvek strömmen vid bit 81. Uppställningen är billig. Pillow skriver varje .webp-fixture plus en .rgba-dump av sin egen avkodning av samma bild; en Pascal-prob och en liten Python-referensmodell loggar en löpande biträknare bredvid varje läsning; den första position där de två loggarna skiljer sig är där felet finns. Börja med ett fixture som aktiverar så lite som möjligt: en platt bild på 32x32 som bara tar den enkla kodvägen. Få den grön, lägg sedan till gradienter, udda dimensioner och alfa, ett fixture i taget. Att gissa bitordning i stället är ett sätt att lägga en dag på detta
Det ärliga förbehållet är att referensen också var fel. Python-modellen glömde att läsa cache_bits och dess transformloop gick inte till slutet, så vissa avvikelsepunkter var referensavkodaren som tappade synk, inte Pascal-avkodaren. Att en referensimplementation är fel gör inte implementationen under test rätt, och ingen sida får fördelen av tvivlet: varje avvikelse måste avgöras mot RFC-texten. Hämta den texten från källan också. Söksammanfattningar förvränger rutinmässigt numeriska tabeller, och avståndskartan med 120 poster, de 14 prediktorlägena och färgcachens multiplikator $1e35a7bd måste alla skrivas av exakt
Var Pascal-heltalsdivision skiljer sig från C
VP8L:s färgtransform är 3,5-fixerad punkt med signerade deltan, och det är där Pascal och C slutar överensstämma. C skiftar negativa heltal aritmetiskt, vilket golvar dem; Pascals div trunkerar mot noll. För varje negativ produkt skiljer de sig med ett, så den inversa färgtransformen driver en kanalnivå per pixel över hela bilden. HotPDF gör därför golvningen uttryckligen i FloorDiv32 i stället för att lita på div
// C skiftar aritmetiskt och golvar negativa värden; Pascal div trunkerar
// mot noll, så det negativa fallet behöver en uttrycklig 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-fixerat punktdelta mellan en transformelementsbyte och en
// färgkanalbyte, båda teckenutvidgade först
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;
Den här typen av fel är värd att namnge eftersom den är osynlig i alla tester vars fixture råkar ge icke-negativa produkter, där div och golvning överensstämmer. Det är också därför HotPDF:s WebP-tester hävdar pixelidentisk likhet mot Pillow-avkodningar av samma filer i stället för en tolerans: gradienter, storleken 100x37 med udda dimensioner, en bild på 40x40 med en riktig alfakanal och en platt bild på 32x32, varje pixel jämförd bit för bit. Ett steg av drift klarar en perceptuell kontroll och misslyckas med en bitvis
Vad WebP-stödet avsiktligt vägrar
HotPDF avkodar den första VP8L-biten i en WebP-fil och inget annat. Förlustgivande VP8-ramar, animationer och varje behållare vars matchande chunk inte är VP8L returnerar False från HPDFDecodeWebPLossless, och AddImage omvandlar det till ett undantag som namnger filen: Failed to decode WebP image (lossless VP8L only). Det är en avsiktlig gräns, inte ett förbiseende: en fil i fel format ska misslyckas där anroparen kan förkonvertera den, i stället för att producera en grå rektangel. Versionsfältet måste vara 0, transformstacken är begränsad till fyra poster och varje gränsöverträdelse ger EWebPDecode, som den publika ingångspunkten omvandlar till ett enkelt False. Avkodning vid import är dessutom motsatt riktning mot att hämta ut bilder ur ett dokument du öppnat, vilket går genom den inlästa bildvägen som beskrivs i extrahering av bilder från en inläst PDF och deras avkodningsfilter. Och varje bildavkodare är en parser som matas med filer du inte skapade: om WebP-resurserna kommer från kunder eller det offentliga internet är gränskontrollerna här golvet snarare än taket, och det starkare svaret är att köra bildkodekar i en isolerad arbetsprocess så att en felaktig ram inte kan ta ner värdprocessen
Det praktiska resultatet är att en Delphi- eller C++Builder-applikation nu kan lägga WebP-resurser i en PDF på samma sätt som PNG: ett anrop till AddImageFromFile, ett anrop till ShowImage och inget extra i installationsprogrammet. Om du vill ha resten av bild- och dokumentpipelinen runt den täcker HotPDF Delphi PDF-komponenten skriv-, läs- och renderingssidorna från samma uppsättning enheter