HotPDF 2.747.0 WebP vaizdus iškoduoja nuo nulio parašytu VP8L (WebP be nuostolių) dekoderiu Object Pascal kalba, todėl THotPDF.AddImageFromFile tiesiogiai priima .webp kelią, be diegiamo libwebp DLL ir be pagalbinio proceso. Dekoderis visiškai įgyvendina RFC 9649 3 skyrių: RIFF konteinerio apėjimą, kanoninius prefiksų kodus, LZ77 atgalines nuorodas, spalvų podėlį ir visas keturias atvirkštines transformacijas. Lossy VP8 kadrai garsiai atmetami, o ne pusiau iškoduojami
Paskata buvo kasdieniška. Dizaino įrankis kiekvieną išteklių eksportuoja kaip WebP, nes tai šiuolaikinė numatytoji forma, ištekliai patenka į sąskaitų ar katalogų generatorių, kuris dešimtmetį be problemų priėmė PNG ir JPEG, ir staiga pusė įvesčių atmetama. Akivaizdus sprendimas – susieti libwebp ir eiti toliau. Tačiau būtent jis savarankišką VCL komponentą paverčia dalyku, turinčiu diegimo istoriją
Kodėl įgyvendinti VP8L, o ne susieti libwebp?
HotPDF kodeką įgyvendina Pascal kalba, nes Delphi komponentas, kurį klientai kompiliuoja į savo vykdomąjį failą, negali tyliai įsigyti vykdymo DLL. Vietinė priklausomybė reiškia 32 ir 64 bitų dvejetainius failus, kuriuos reikia sekti, versiją, kurią reikia prisegti, kodo pasirašymo grandinę, kurią reikia paaiškinti diegimą vykdančiam žmogui, ir dar vieną failą, kurį antivirusinė programa užrakintame terminale gali nuspręsti nemėgstanti. Komponentui, kurio pagrindinis privalumas – įdėti į projektą ir naudoti, tai tikra, o ne teorinė kaina. Kita argumento pusė yra ta, kad VP8L nedidelis: prefiksų kodų ir LZ77 formatas su keturiomis atvirkštinėmis transformacijomis bei 120 elementų kaimyninių atstumų lentele, o visas dekoderis HPDFWebP.pas faile užima mažiau nei 900 Pascal eilučių. THotPDF.AddImage WebP šaka yra tame pačiame plėtinio parinkimo taške, kuris .jp2, .j2k, .jpt ir .jpc jau nukreipia į JPEG 2000 kelią, todėl santechnika jau buvo paruošta, ten pat, kur aprašyta JPEG 2000 vaizdų pridėjimo prie PDF Delphi aplinkoje. Iškvietėjai, norintys ne PDF vaizdo, o neapdorotų pikselių, gali tiesiai kviesti HPDFDecodeWebPLossless, kuris užpildo TWebPCardinalArray $AARRGGBB reikšmėmis skenavimo eilučių tvarka
uses
HPDFDoc, HPDFWebP;
var
Pdf: THotPDF;
Idx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalog.pdf';
Pdf.BeginDoc;
// .webp nukreipiamas į integruotą VP8L dekoderį, be DLL
Idx := Pdf.AddImageFromFile('product-shot.webp', icFlate);
Pdf.CurrentPage.ShowImage(Idx, 50, 500, 240, 180, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Kodėl VP8L bitų srautas vienu metu skaitomas dviem kryptimis?
Nes konteinerio bitų tvarka ir prefiksų kodo bitų tvarka specifikuojamos nepriklausomai, o VP8L joms pasirenka priešingas konvencijas. RFC 9649 3.2 skyrius aiškiai sako, kad bitų srautas skaitomas nuo mažiausiai reikšmingo bito: skaitytuvas pradeda nuo 0 bito baite ir juda aukštyn. Kanoniniai prefiksų kodai, esantys šiame sraute, ateina nuo labiausiai reikšmingo bito, medžio šaknis pirmiausia, todėl dekodavimo eiga akumuliatorių stumia kairėn ir naują bitą OR operacija įkelia apačioje. Taigi skaitytuvas ir kodo apėjimas tame pačiame cikle juda priešingomis kryptimis, o skaitant kodą iš naujo tai kaskart atrodo kaip klaida
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;
// kanoninis apėjimas eina kita kryptimi: pirmas nuo srauto nuimtas bitas
// yra aukščiausiai reikšmingas kodo bitas
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;
Trys RFC detalės, tyliai išsynchronizuojančios srautą
Trys RFC 9649 semantikos dalykai joje įvardijami tik po vieną kartą, juos lengva praleisti, o kiekvienas kainuoja arba sutaupo vieną bitą, kurio pakanka visas vėlesnes lenteles paversti triukšmu. Visi trys rasti HotPDF VP8L dekoderiuje, ir visi trys sukelia tą patį požymį: įtikinamai atrodantį vaizdą, kuris visur neteisingas
- Entropijos būdu koduojamas vaizdas ne pagrindiniame vaidmenyje visai nerašo meta prefikso bito.
entropy-coded-imageABNF elemente jo tiesiog nėra, todėl nuskaičius vieną bitą srautas paslenkamas. HotPDFAllowMeta = Falseperduoda pačiam entropijos vaizdui, predikcijos ir spalvų transformacijos duomenims bei spalvų indeksavimo paletei - Vieno lapo prefiksų kodas sunaudoja nulį bitų. RFC 9649 3.7.2.1 tai sako tiesiogiai, o kanoninis apėjimas mielai perskaitytų bitą ir tada nesugebėtų jo panaudoti, todėl
BuildHuffaptinka bendrą vieno simbolio skaičių ir medį pažymiSingle, dekoduodamas tą vieną simbolį nepaliesdamas skaitytuvo - cache_bits reikšmė 0 reiškia, kad spalvų podėlio dydis yra 0, o ne
1 shl 0. Patogus poslinkis duoda 1, todėl žalioji abėcėlė256 + 24 + CacheSizetampa 281 vietoje 280, ir kiekviena po jos skaitoma prefiksų kodų lentelė pasislenka
CacheBits := 0;
CacheSize := 0; // cache_bits = 0 iš tikrųjų reiškia, kad podėlio nėra
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: tik erdviškai koduojamas (ARGB) vaizdas turi
// meta prefikso bitą; entropijos vaidmenys jo niekada nerašo
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);
Per paruošiamąjį testinį failą šie trys dalykai išryškėjo atitinkamai 47, 81 ir 89 bite. Šių skaičių esmė yra šiame skyriuje. Nė vienas nepasireiškė kaip vieneto paklaida; kiekvienas atrodė kaip vaizdas, kuris sėkmingai iškoduojamas iki galo ir visur atrodo kaip statinis triukšmas, o vienintelis juos atskyręs dalykas buvo tiksli bito pozicija, kurioje srautas nustojo sutapti su etalonu
Ką duoda diff pagal bito poziciją?
Diff pagal bito poziciją nenaudingą klausimą paverčia vienos eilutės klausimu: ne „kodėl šis vaizdas neteisingas“, o „kodėl srautas išsiskyrė ties 81 bitu“. Paruošimas pigus. Pillow parašo kiekvieną .webp testinį failą ir .rgba išklotinę, gautą iš savo to paties vaizdo dekoderio; Pascal zondas ir nedidelis Python etaloninis modelis prie kiekvieno skaitymo įrašo einamą bitų skaitiklį; pirmoji pozicija, kurioje abu žurnalai nesutampa, ir yra klaidos vieta. Pradėkite nuo testo, kuris įjungia kuo mažiau: plokščias 32 x 32 vaizdas, einantis tik paprasto kodo keliu. Gavę teisingą žalią rezultatą, po vieną pridėkite gradientus, keistus matmenis ir alfa kanalą. Spėlioti bitų tvarką vietoj to – būdas praleisti dieną
Sąžininga pastaba: etalonas irgi buvo klaidingas. Python modelis pamiršo perskaityti cache_bits, o transformacijos ciklas nebuvo užbaigtas, todėl kai kurie išsiskyrimo taškai reiškė etaloninio dekoderio sinchronizacijos praradimą, o ne Pascal klaidą. Klaidinga etaloninė realizacija nepadaro tikrinamos realizacijos teisinga, ir nė viena pusė negauna abejonių naudos: kiekvienas išsiskyrimas turi būti įvertintas pagal RFC tekstą. Tą tekstą taip pat imkite iš šaltinio. Paieškos santraukos dažnai iškraipo skaitines lenteles, o 120 elementų atstumų lentelę, 14 predikcijos režimų ir spalvų podėlio daugiklį $1e35a7bd reikia perrašyti tiksliai
Kur Pascal sveikųjų dalyba skiriasi nuo C?
VP8L spalvų transformacija yra 3.5 fiksuoto taško su ženklo delta, ir būtent čia Pascal bei C nustoja sutarti. C neigiamus sveikuosius stumia aritmetiškai, o tai reiškia grindis; Pascal div apvalina link nulio. Kiekvieno neigiamo sandaugos rezultato atveju jie skiriasi vienetu, todėl atvirkštinė spalvų transformacija per visą vaizdą kiekviename pikselie nukrypsta vienu kanalo žingsniu. Todėl HotPDF FloorDiv32 aiškiai įgyvendina grindis, o ne pasikliauja div
// C stumia aritmetiškai ir neigiamus skaičius apvalina žemyn; Pascal div apvalina
// link nulio, todėl neigiamam atvejui reikia aiškios pataisos
function FloorDiv32(V: Integer): Integer;
begin
Result := V div 32;
if (V < 0) and (V mod 32 <> 0) then
Dec(Result);
end;
// 3.5 fiksuoto taško delta tarp transformacijos elemento baito ir
// spalvos kanalo baito, abu pirmiausia išplečiami su ženklu
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;
Šį defektų tipą verta įvardyti, nes jis nematomas teste, kurio testiniai failai atsitiktinai sukuria tik neneigiamas sandaugas, o ten div ir grindys sutampa. Tai taip pat priežastis, dėl kurios HotPDF WebP testai tikrina pikselių tapatumą su Pillow iškodavimais iš tų pačių failų, o ne toleranciją: gradientai, keistas 100 x 37 dydis, 40 x 40 vaizdas su tikru alfa kanalu ir plokščias 32 x 32 vaizdas – kiekvienas pikselis lyginamas bito tikslumu. Vieno žingsnio poslinkis praeina suvokimo testą ir nepraeina bitinio
Ko WebP palaikymas sąmoningai atsisako?
HotPDF iškoduoja pirmą WebP failo VP8L fragmentą ir nieko daugiau. Lossy VP8 kadrai, animacijos ir konteineriai, kurių atitinkamas fragmentas nėra VP8L, iš HPDFDecodeWebPLossless grąžina False, o AddImage tai paverčia išimtimi, kurioje įvardijamas failas: Failed to decode WebP image (lossless VP8L only). Tai sąmoninga riba, o ne apsileidimas: netinkamo formato failas turi žlugti ten, kur iškvietėjas gali jį iš anksto konvertuoti, o ne pagaminti pilką stačiakampį. Versijos laukas turi būti 0, transformacijų stekas apribotas keturiais įrašais, o kiekvienas ribų pažeidimas kelia EWebPDecode, kurią viešasis entry point paverčia paprastu False. Importo metu atliekamas dekodavimas taip pat yra priešinga kryptis nei vaizdų ištraukimas iš atverto dokumento, kuris eina per įkelto vaizdo kelią, aprašytą išgaunant vaizdus iš įkelto PDF ir jų dekodavimo filtrus. Be to, bet kuris vaizdų dekoderis yra parseris, maitinamas failais, kurių nesukūrėte jūs: jei WebP išteklius siunčia klientai arba viešas internetas, čia esantys ribų patikrinimai yra tik minimumas, o stipresnis sprendimas yra paleisti vaizdų kodekus izoliuotame worker procese, kad netinkamas kadras nenutrauktų pagrindinio proceso
Praktiškai Delphi arba C++Builder programa dabar gali dėti WebP išteklius į PDF taip pat kaip PNG: vienas AddImageFromFile iškvietimas, vienas ShowImage iškvietimas ir nieko papildomo diegimo programoje. Jei norite likusio vaizdų ir dokumentų konvejerio, supančio šią funkciją, HotPDF Delphi PDF komponentas apima rašymą, įkėlimą ir atvaizdavimą iš to paties unitų rinkinio