Tok, označen z /Predictor 12, ne pomeni, da vsaka vrstica uporablja filter PNG 2. HotPDF, izvorna komponenta VCL PDF za Delphi in C++Builder, vrednosti predictorja od 10 do 15 obravnava kot eno družino: dejanska oznaka filtra, 0 do 4, je prvi bajt vsake kodirane vrstice, HPDFDecodePredictor pa to oznako bere in preverja vrstico za vrstico. To razlikovanje je oblika skoraj vsakega hrošča v tem kotu PDF-ja, ker nič ne javi napake, ko ga naredite napačno. Veriga filtrov teče, raster je velikosti, ki ste jo pričakovali, slika pa izpade kot diagonalen šum ali gradient, ki z vsako vrstico odnosi bolj stran. Pet števil v /DecodeParms (ISO 32000-1 §7.4.4) večinoma spremeni pomen bajtov, ne pa njihove dolžine, zato napačno število proizvede verjetne videti smeti namesto napake
Zakaj /Predictor 12 ne pomeni PNG filtra 2 na vsaki vrstici?
Ker številka predictorja pove le "v uporabi je predikcija PNG", ne pa katerega filtra. Kodirniki PNG izberejo filter za vsako skenirno vrstico, filter PDF pa to podeduje, tako da se vrednosti predictorja 10 (Brez), 11 (Sub), 12 (Up), 13 (Average), 14 (Paeth) in 15 (Optimum) vse dekodirajo enako: vodilni oznakovni bajt vsake vrstice je tisto, kar mora dekodirnik upoštevati. Posledica postavitve je enako pomembna kot semantika. Vsaka kodirana vrstica je dolga 1 + RowBytes bajtov, vhod torej presega izhod natanko za število vrstic, tok, katerega dolžina ni celoštevilski večkratnik RowBytes + 1, pa je po definiciji prirezan. HotPDF to mejo preveri, preden se dotakne bajta, zavrne vsako oznako nad 4 z Invalid PNG predictor row tag in prejšnjo vrstico bere neposredno iz enotnega izhodnega medpomnilnika namesto materializacije dvodimenzionalnega polja vrstic. Filtra 1 in 3 segata nazaj BytesPerPixel znotraj trenutne vrstice, filter 2 bere naravnost navzgor, filter 4 izvede izbiro Paeth prek leve, zgornje in zgornje-leve — vsi štirje pa delujejo na že rekonstruiranem izhodu, zato mora biti zgornja vrstica dekodirana vrstica in nikoli filtriran vhod
uses
HPDFPredictor;
var
Filtered, Raster: AnsiString;
ErrorText: string;
begin
// /DecodeParms << /Predictor 12 /Colors 3 /BitsPerComponent 8 /Columns 1024 >>
if HPDFTryDecodePredictor(Filtered, 12, 3, 8, 1024,
Int64(1024) * 3 * 8192, Raster, ErrorText) then
ConsumeRaster(Raster)
else
LogStreamDefect('predictor', ErrorText); // no exception, no partial raster
end;
Argument MaxOutputBytes ni okrasek. Stopnja predictorja je zamaskirana stopnja dekompresije, sovražna ali le pokvarjena vrednost /Columns pa nekaj kilobajtov vhoda spremeni v zahtevo po dodelitvi več gigabajtov. HotPDF najprej izračuna bite na vrstico, bajte na vrstico in skupno velikost rastra v Int64, zavrne geometrijo, ki preplavi, in upošteva strop, ki ga poda klicatelj. Podajte pravo mejo, izpeljano iz slovarja slike, in način odpovedi postane zabeleženo sporočilo namesto pogovornega okna o pomanjkanju pomnilnika na stranki mašini
Zakaj TIFF Predictor 2 pokvari 4-bitne slike?
Ker je Predictor 2 vodoravno diferenciranje po vzorcu, ne po bajtu, pri 1, 2 ali 4 bitih na komponento pa si več vzorcev deli bajt. Pogosta implementacija doda bajt N-Colors bajtu N, kar je slučajno pravilno pri 8 bitih na komponento in tiho napačno povsod drugje. 8-bitno skeniranje RGB se dekodira popolnoma pravilno, nato pa ista koda uniči 4-bitno indeksirano sliko, prvič ko se ta pojavi v produkciji
Pravilna aritmetika deluje znotraj bitnega polja. HotPDF prehodi vzorce od indeksa Colors do Colors * Columns - 1, izvleče vzorec in njegovega levega soseda iste komponente z masko (1 shl BitsPerComponent) - 1 pri ustreznem premiku, ju sešteje po modulu te maske in rezultat zapiše nazaj, ne da bi motil druge vzorce, zapakirane v isti bajt. Pomemben je tudi rep: vrstica je zapolnjena do meje bajta, zato morajo zapolnitveni biti za zadnjim vzorcem preživeti nedotaknjeni namesto zloženi v aritmetiko. Pri 16 bitih na komponento je vsak vzorec par bajtov big-endian, seštevanje pa se prelije pri $FFFF čez celoten par namesto neodvisnega prenosa med bajti; pri 8 bitih je preprosta bajtna rekurenca pravilna, s korakom Colors, tako da se rdeča akumulira proti rdeči in alfa proti alfi. V vsaki varianti je prvi piksel vrstice dobesedna vrednost, nikoli razlika, rekurenca pa se znova zažene na vsaki meji vrstice — predikcija TIFF nikoli ne bere zgornje vrstice, kar je cela razlika med njo in družino PNG
Kaj EarlyChange resnično nadzoruje v LZWDecode?
Nadzoruje, kdaj bralnik razširi velikost kode za en bit, zamik za eno kodo pa pokvari vse, kar sledi. HotPDF pravilo izrazi kot eno samo invarianto: po dodajanju vnosa v slovar se naslednje branje razširi, ko NextCode doseže (1 shl CodeSize) - Ord(EarlyChange). Pri /EarlyChange 1, privzeti vrednosti ISO 32000-1 §7.4.4, se preklop zgodi eno kodo prezgodaj; pri /EarlyChange 0 se zgodi natanko na meji. Obe se pojavljata v resničnih datotekah in nič v bitnem toku ne pove, katero je kodirnik uporabil. Preostanek avtomata stanj se mora premikati usklajeno: koda za čiščenje ponastavi velikost kode, bitno masko, naslednjo prosto kodo in shranjevanje fraz skupaj, koda za konec informacij pa je prebrana pri kateri koli širini, ki je trenutno veljavna v tistem trenutku, ne pa pri začetnih 9 bitih. HotPDF se začne pri InitialCodeSize 9, omeji velikost kode na 12 in slovar na 4096 vnosov, FillOrder pa privzeto nastavi na foTop, ker PDF pakira kode z bitom najvišjega reda naprej — foBottom obstaja za tokove v slogu TIFF, ki tega ne počnejo
uses
HPDFLZW;
var
Decoder: TPDFLZWDecompressor;
Parms: TPDFLZWParms;
Plain: AnsiString;
begin
Decoder := TPDFLZWDecompressor.Create;
try
Decoder.EarlyChange := True; // /EarlyChange 1 is the PDF default
Decoder.FillOrder := foTop; // high-order bit first
Decoder.MaxOutputBytes := 256 * 1024 * 1024;
Decoder.RequireInitialClear := False;
Decoder.RequireEndOfInformation := False;
Parms.Predictor := 12;
Parms.Colors := 3;
Parms.BitsPerComponent := 8;
Parms.Columns := 1024;
Parms.ExpandedTo8Bit := False;
Parms.ColorSpace := 'DeviceRGB';
if Decoder.TryDecompress(RawStreamBytes, Parms, Plain) then
LogDecodeStats(Decoder.PeakCodeSize, Decoder.DictionaryAdds,
Decoder.KwKwKExpansions, Decoder.OutputBytes)
else
LogStreamDefect('lzw', Decoder.LastError);
finally
Decoder.Free;
end;
end;
Statistika obstaja za triažo, ne za ničemer. Ko se datoteka dekodira na pravilno dolžino, a napačne piksle, PeakCodeSize in DictionaryAdds takoj povesta, ali se je bralnik kdaj razširil tam, kjer se je pisec. Preklopite EarlyChange, znova dekodirajte, primerjajte oba: če se številke premaknejo, imate odgovor v enem zagonu namesto koraka za korakom skozi bitni bralnik
Veja KwKwK in kdaj naj tok preprosto odpove
Edini zakonit primer, ki je videti nezakonit, je Code = NextCode, HotPDF pa ga obravnava tako, da vnos zgradi, preden ga izda. Kodirnik lahko izda kodo za frazo, ki jo definira v istem koraku, kar se zgodi, kadarkoli vhod vsebuje vzorec oblike K w K w K; dekodirnik te kode ne more poiskati, ker še ne obstaja, zato mora zgraditi Previous + First(Previous), ga dodati kot nov vnos in izdati vnos, ki ga je pravkar ustvaril. HotPDF te šteje v KwKwKExpansions in navzkrižno preveri, da je koda, ki jo je dodal, koda, ki je bila zahtevana. Vse nad NextCode je poškodba, tam pa mora dekodirnik prenehati, ne pa improvizirati: HotPDF javi napako pri prihodnji kodi, pri predponi slovarja, ki kaže zunaj arene fraz, pri polnem slovarju in pri prvi kodi, ki ni dobesedna vrednost. Dve stikali za strogost sta privzeto namerno izklopljeni, RequireInitialClear in RequireEndOfInformation, ker veliko produkcijskih PDF-jev izpusti vodilno kodo za čiščenje ali zmanjka podatkov brez terminatorja. Vklopite ju pri preverjanju lastnega izhoda, pustite ju izklopljeni pri porabljanju datotek iz divjine
Kje je /DecodeParms dejansko brana na strani naloženega dokumenta
HotPDF razreši /DecodeParms ali njegovo okrajšavo /DP na slovarju toka slike, sprejme bodisi slovar bodisi polje in vzame zadnji element, kadar je to polje, nato pa v pot rastra prenese Predictor, Colors, BitsPerComponent, Columns in EarlyChange. Primer polja je tisti, ki ga ljudje pozabijo: tok, filtriran z [/ASCII85Decode /FlateDecode], nosi vzporedno polje parametrov, nastavitve predictorja pa pripadajo zadnjemu filtru, ne prvemu
var
Pdf: THotPDF;
Info: THPDFLoadedImageInfo;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned.pdf', '') > 0 then
for I := 0 to Pdf.GetLoadedImageCount - 1 do
if Pdf.GetLoadedImageInfo(I, Info) then
begin
Bmp := Pdf.ExtractLoadedImage(I); // nil when the raster is unusable
if Bmp <> nil then
try
Bmp.SaveToFile(Format('image-%d.bmp', [I]));
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Vredno je poimenovati eno zgodovinsko napako na tej poti, ker se razred hrošča ponavlja. Stara rutina Flate s parametri je ustvarila tok dekompresije in nato kopirala iz izvirnega stisnjenega vhoda, tako da je stopnja predictorja prejela stisnjene bajte in jih vestno predikcijsko obdelala nazaj: vedno napačno, nikoli javljena napaka. Trenutna koda bere le iz dekodirnika, preden rezultat preda skupnemu predictorju, in zavrne raster, krajši od izračunane velikosti, namesto da bi se zatekla nazaj k še vedno stisnjenim bajtom — nadomestna pot, ki je nekoč spremenila napako dekodiranja v poškodovano bitno sliko. Ista implementacija predictorja zdaj streže tudi tokovom navzkrižnih referenc, kar je koristna doslednost, če delate tudi z tokovi objektov in prirastnimi posodobitvami, okoliška mehanika izvlečka pa je obravnavana v spremljajočem članku o izvlečenju naloženih slik in njihovih filtrov dekodiranja. Slike, ki prispejo kot DCTDecode ali JPXDecode, nikoli ne dosežejo predictorja; nosijo svoj lasten stisnjen piksel model
Prepustnost: strnjena arena fraz proti nizom po vnosih
Zamenjava slovarja nizov po vnosih s strnjeno areno fraz je izmerila približno 1,61-krat hitrejši rezultat na patološkem vhodu: 1558 MiB/s proti 969 MiB/s na benchmarku, čigar ena sama najdaljša fraza doseže 7.370.880 bajtov. Oblika tega vhoda pojasni razliko, ker klasične implementacije izberejo eno od dveh slabih menjav. Slovar vrednosti AnsiString dodeli in kopira svež niz za vsakega od do 4096 vnosov, vsak nov vnos pa kopira svojega starša v celoti; sklad predpon in pripon se temu pomnilniku povsem izogne, vendar vsako frazo rekonstruira s prehodom po verigi nazaj po en bajt naenkrat in njenim obračanjem, kar je v redu za navadno besedilo in boleče, ko ena fraza doseže megabajte. HotPDF vsako frazo doda strnjeno v geometrijsko rastočo areno, vnose indeksira po odmiku in dolžini ter frazo izda z enim samim Move v izhodni medpomnilnik. Poštena cena je pomnilnik: arena, ki v celoti drži vsako frazo, je omejena z vsoto vseh dolžin fraz, ne pa s številom vnosov, kar je natanko razlog, da MaxOutputBytes obstaja tako na dekompresorju kot na predictorju. Izpeljite to mejo iz tega, kar slovar slike trdi, da bi raster moral biti, in lažniv tok hitro odpove
Dekompresor LZW, skupni predictor in tukaj prikazana pot izvlečka naloženih slik so izdani kot del standardne HotPDF Component za Delphi in C++Builder, s celotnim referenčnim opisom filtra in DecodeParms na strani izdelka