Egy /Predictor 12-vel jelölt stream nem azt jelenti, hogy minden sor a PNG 2-es szűrőt használja. A HotPDF, a Delphi és C++Builder natív VCL PDF komponense, a 10-től 15-ig terjedő predictor-értékeket egy családként kezeli: a valódi szűrő-tag, 0-tól 4-ig, minden kódolt sor első bájtja, és a HPDFDecodePredictor soronként olvassa és érvényesíti ezt a tag-et. Ez a megkülönböztetés a PDF eme szegletében szinte minden hiba alakja, mert semmi nem jelez, ha rosszul csinálod. A szűrőlánc lefut, a raszter a várt méretű, és a kép átlós zajként vagy soronként egyre jobban elcsúszó gradiensként jön ki. Az öt szám a /DecodeParms-ban (ISO 32000-1 §7.4.4) többnyire a bájtok jelentését változtatja meg, nem a hosszukat, így egy rossz szám hihető szemetet produkál hiba helyett
Miért nem jelenti a /Predictor 12 azt, hogy minden soron a PNG 2-es szűrő fut?
Mert a predictor-szám csak azt mondja: "PNG predikció van használatban", nem azt, melyik szűrő. A PNG kódolók soronként választanak szűrőt, és a PDF szűrő örökli ezt, így a 10 (None), 11 (Sub), 12 (Up), 13 (Average), 14 (Paeth) és 15 (Optimum) predictor-értékek mind azonosan dekódolódnak: a decodernek engedelmeskednie kell minden sor vezető tag-bájtjának. Az elrendezés következménye ugyanannyira számít, mint a szemantika. Minden kódolt sor 1 + RowBytes bájt hosszú, a bemenet ezért pontosan a sorok számával haladja meg a kimenetet, és egy stream, amelynek hossza nem RowBytes + 1 egész számú többszöröse, definíció szerint csonkolt. A HotPDF ellenőrzi ezt a határt, mielőtt egyetlen bájthoz is hozzáérne, elutasít bármely 4 fölötti tag-et Invalid PNG predictor row tag hibával, és az előző sort közvetlenül az egyetlen kimeneti pufferből olvassa, ahelyett hogy egy kétdimenziós sortömböt anyagosítana. Az 1-es és 3-as szűrők visszanyúlnak BytesPerPixel-lel az aktuális soron belül, a 2-es szűrő egyenesen felfelé olvas, a 4-es szűrő a Paeth-választást futtatja bal, fel és fel-bal fölött — és mind a négy már rekonstruált kimeneten dolgozik, ami miatt a fel-sornak a dekódolt sornak kell lennie, sosem a szűrt bemenetnek
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;
A MaxOutputBytes argumentum nem dísz. Egy predictor-lépés valójában egy dekompressziós lépés álcázva, és egy ellenséges vagy csupán hibás /Columns érték néhány kilobájtnyi bemenetet többgigabájtos allokációs kéréssé alakíthat. A HotPDF Int64-ben számítja ki a soronkénti biteket, a soronkénti bájtokat és a teljes raszterméretet elsőként, elutasítja azt a geometriát, amely túlcsordul, és tiszteletben tartja a hívó által megadott plafont. Adj át egy valódi, a kép-szótárból származó korlátot, és a hibamód naplózott üzenet lesz kimenet-nélküli-memória párbeszédablak helyett egy ügyfél gépén
Miért rongálja meg a TIFF Predictor 2 a 4-bites képeket?
Mert a Predictor 2 vízszintes differenciálás mintánként, nem bájtonként, és 1, 2 vagy 4 bit komponensenként több minta oszt meg egy bájtot. A gyakori megvalósítás hozzáadja az N-Colors bájtot az N bájthoz, ami 8 bit komponensenkénti esetben véletlenül helyes, és mindenhol máshol csendben rossz. Egy 8-bites RGB scan tökéletesen dekódolódik, majd ugyanez a kód tönkreteszi az első 4-bites indexelt képet, amint az megjelenik a gyakorlatban
A helyes aritmetika a bitmezőn belül dolgozik. A HotPDF bejárja a mintákat a Colors indextől a Colors * Columns - 1-ig, kinyeri a mintát és annak ugyanazon komponens szerinti bal szomszédját egy (1 shl BitsPerComponent) - 1 maszkkal a megfelelő eltoláson, összeadja őket modulo ezt a maszkot, és visszaírja az eredményt anélkül, hogy megzavarná a bájtba csomagolt többi mintát. A farokrész is számít: egy sor bájthatárig van kitöltve, így az utolsó minta utáni kitöltő biteknek érintetlenül kell túlélniük ahelyett, hogy beleolvadnának az aritmetikába. 16 bit komponensenként minden minta egy big-endian bájtpár, és az összeadás $FFFF-nél csavarodik a páron keresztül ahelyett, hogy bájtonként külön hordozódna át; 8 biten az egyszerű bájt-rekurzió a helyes, Colors-onkénti lépésekkel, így a piros a piros ellen halmozódik, az alfa az alfa ellen. Minden változatban egy sor első pixele literál, sosem különbség, és a rekurzió minden sorhatárnál újraindul — a TIFF predikció sosem olvassa a fölötte lévő sort, ami a teljes különbség közte és a PNG család között
Mit vezérel valójában az EarlyChange az LZWDecode-ban?
Azt vezérli, mikor szélesíti ki az olvasó a kódméretét egy bittel, és egy kódnyi eltérés mindent megrongál, ami utána következik. A HotPDF egyetlen invariánsként fejezi ki a szabályt: egy szótárbejegyzés hozzáadása után a következő olvasás akkor szélesít, amikor a NextCode eléri a (1 shl CodeSize) - Ord(EarlyChange)-t. /EarlyChange 1-gyel, az ISO 32000-1 §7.4.4 alapértelmezésével, a váltás egy kóddal korábban történik; /EarlyChange 0-val pontosan a határon történik. Mindkettő megjelenik valódi fájlokban, és semmi a bitfolyamban nem mondja meg, melyiket használta a kódoló. Az állapotgép többi részének lépést kell tartania: egy clear kód visszaállítja a kódméretet, a bitmaszkot, a következő szabad kódot és a kifejezés-tárolót együtt, és a végjel-kód azon a szélességen kerül beolvasásra, amely abban a pillanatban aktuális, nem a kezdeti 9 biten. A HotPDF InitialCodeSize 9-cel indul, 12-re korlátozza a kódméretet és 4096 bejegyzésre a szótárat, és alapértelmezésben foTop-ra állítja a FillOrder-t, mert a PDF a kódokat magas-rendű bit elsőként csomagolja — a foBottom a TIFF-stílusú streamekhez létezik, amelyek nem ezt teszik
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;
A statisztikák a triázsért léteznek, nem hiúságból. Amikor egy fájl a helyes hosszra dekódolódik, de rossz pixelekre, a PeakCodeSize és a DictionaryAdds azonnal megmondja, hogy az olvasó valaha is ott szélesített-e, ahol az író. Fordítsd meg az EarlyChange-et, dekódolj újra, hasonlítsd össze a kettőt: ha a számok mozognak, egy futásból megvan a válaszod, ahelyett hogy egy bitolvasón lépkednél végig
A KwKwK ág, és amikor egy streamnek egyszerűen el kell buknia
Az egyetlen legális eset, amely illegálisnak tűnik, a Code = NextCode, és a HotPDF úgy kezeli, hogy a bejegyzést a kibocsátás előtt felépíti. Egy kódoló kibocsáthatja egy olyan kifejezés kódját, amelyet ugyanabban a lépésben definiál, ami akkor történik, amikor a bemenet K w K w K alakú mintát tartalmaz; a dekódoló nem tudja megkeresni ezt a kódot, mert az még nem létezik, így fel kell építenie a Previous + First(Previous)-t, hozzá kell adnia mint új bejegyzést, és ki kell bocsátania az éppen létrehozott bejegyzést. A HotPDF ezeket a KwKwKExpansions-ben számolja, és keresztellenőrzi, hogy az általa hozzáadott kód az-e, amit kértek tőle. Minden, ami a NextCode fölött van, korrupció, és ott egy dekódolónak inkább meg kell állnia, mint improvizálnia: a HotPDF kivételt dob egy jövőbeli kódra, egy szótár-előtagra, amely a kifejezés-arénán kívülre mutat, egy teli szótárra, és egy első kódra, amely nem literál. Két szigorítási kapcsoló szándékosan alapból ki van kapcsolva, a RequireInitialClear és a RequireEndOfInformation, mert rengeteg éles PDF kihagyja a vezető clear kódot, vagy adatvég nélkül fut ki egy záró-jel nélkül. Kapcsold be őket a saját kimeneted validálásához, hagyd kikapcsolva, ha vadon élő fájlokat fogyasztasz
Ahol a /DecodeParms ténylegesen olvasásra kerül a betöltött-dokumentum oldalon
A HotPDF feloldja a /DecodeParms-ot vagy annak /DP rövidítését a kép stream-szótárán, elfogad akár egy szótárat, akár egy tömböt, és az utolsó elemet veszi, ha tömb, majd átviszi a Predictor, Colors, BitsPerComponent, Columns és EarlyChange értékeket a raszter-útvonalra. A tömb-eset az, amit az emberek elfelejtenek: egy [/ASCII85Decode /FlateDecode] által szűrt stream párhuzamos paramétertömböt hordoz, és a predictor-beállítások az utolsó szűrőhöz tartoznak, nem az elsőhöz
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;
Egy régi hibát érdemes megnevezni ezen az úton, mert a hibaosztály ismétlődik. A régi Flate-paraméterekkel-rendelkező rutin dekompressziós streamet hozott létre, majd az eredeti tömörített bemenetből másolt, így a predictor-lépés tömörített bájtokat kapott, és lelkiismeretesen "kipredikálta" őket: mindig rossz, sosem jelzett hibát. A jelenlegi kód csak a dekóderből olvas, mielőtt átadná az eredményt a megosztott predictornak, és elutasít egy a számított méretnél rövidebb rasztert, ahelyett hogy visszaesne a még tömörített bájtokra — egy visszaesés, amely korábban egy dekódolási hibát korrupt bitmappé alakított. Ugyanez a predictor-megvalósítás most kereszthivatkozási streameket is kiszolgál, ami hasznos konzisztencia, ha az objektumfolyamokkal és inkrementális frissítésekkel is dolgozol, és a körülvevő kinyerő-gépezetet a betöltött képek és decode-szűrőik kinyeréséről szóló kísérőcikk tárgyalja. A DCTDecode-ként vagy JPXDecode-ként érkező képek sosem érik el a predictort; saját tömörített pixelmodellt hordoznak
Átviteli sebesség: egy folytonos kifejezés-aréna kontra bejegyzésenkénti sztringek
A bejegyzésenkénti sztring-szótár lecserélése egy folytonos kifejezés-arénára nagyjából 1,61-szer gyorsabbnak mérődött egy patológiás bemeneten: 1558 MiB/s a 969 MiB/s ellen egy olyan benchmarkon, amelynek egyetlen leghosszabb kifejezése 7 370 880 bájtig ér. Ennek a bemenetnek az alakja megmagyarázza a különbséget, mert a klasszikus megvalósítások két rossz csere közül választanak. Egy AnsiString értékekből álló szótár friss sztringet allokál és másol minden egyes, akár 4096 bejegyzés esetén, minden új bejegyzés a teljes szülőjét másolva; egy előtag/utótag-verem elkerüli ezt a memóriát, de minden kifejezést a lánc visszafelé bájtonkénti bejárásával és megfordításával épít fel újra, ami rendben van hétköznapi szövegnél, és fájdalmas, amikor egy kifejezés megabájtokra rúg. A HotPDF folytonosan hozzáfűzi minden kifejezést egy geometriailag növekvő arénához, eltolás és hossz alapján indexeli a bejegyzéseket, és egyetlen Move-val bocsát ki egy kifejezést a kimeneti pufferbe. Az őszinte ára a memória: egy minden kifejezést teljes egészében tartó aréna a kifejezéshosszak összegével korlátozott, nem a bejegyzésszámmal, ami pontosan az oka, hogy a MaxOutputBytes létezik mind a dekompresszoron, mind a predictoron. Vezesd le ezt a korlátot abból, amit a kép-szótár állít a raszterről, és egy hazudó stream gyorsan elbukik
Az LZW dekompresszor, a megosztott predictor és az itt bemutatott betöltött-kép-kinyerő útvonal a szabványos HotPDF Component részeként érkezik Delphihez és C++Builderhez, a teljes szűrő- és DecodeParms-referenciával a termékoldalon