HotPDF, nativní PDF komponenta pro Delphi, dekóduje obrázky CCITTFaxDecode s plnou podporou rozšíření nekomprimovaného módu T.4/T.6 a s obnovou poškozených řádků řízenou přes DamagedRowsBeforeError. Do nekomprimovaného módu se vstupuje jen rozšiřujícím kódem končícím na 111 a poškozené řádky se opravují jen u dat Group 3 s nastaveným EndOfLine. Všechno ostatní selže čistě místo vyrobit zkosenou stránku
Faxem kódované obrázky jsou dlouhý ocas dekódování PDF obrázků. Většina CCITT toků, které potkáte, vyšla ze skenerového ovladače nebo z dokumentové capture pipeline, která emituje jen obyčejné Group 4, a dekodér zvládající běžný případ projde každým testem, který na něj hodíte. Pak přijde archiv z patnáct let starého faxového serveru, stránka se vykreslí jako šikmý šum od řádku 400 dolů a zjistíte, že právě ta dvě místa doporučení ITU-T, která nikdo neimplementuje, ten soubor používá. Tenhle článek oběma místy projde tak, jak je HotPDF řeší, a podívá se i na místa, kde se dekodér může splést, aniž by přestal vypadat správně
Které parametry CCITTFaxDecode doopravdy mění bitový tok?
Čtyři položky ve slovníku DecodeParms mění to, jak dekodér rámuje bitový tok: K, EndOfLine, EncodedByteAlign a DamagedRowsBeforeError. Definuje je ISO 32000-1 §7.4.6 (tabulka 11) a chyba v kteréhokoli posune každý řádek za první neshodou. BlackIs1, Columns, Rows a EndOfBlock mají taky význam, ale dotýkají se polarity, geometrie a místa, kde dekódování skončí, spíš než způsobu, jakým se najde hranice řádku
Kvybírá kódovací schéma: negativní znamená čistě dvourozměrné Group 4 (T.6), nula jednorozměrné Modified Huffman (Group 3 1D) a kladné smíšené Group 3 2D, kde tag bit za každým EOL říká, zda další řádek je 1D nebo 2DEndOfLineříká, že každému řádku Group 3 předchází kód EOL, jedenáct nulových bitů následovaných jedničkouEncodedByteAlignříká, že každý zakódovaný řádek začíná na hranici bajtu, takže dekodér musí výplňové bity vyhodit, než ho přečteDamagedRowsBeforeErrorříká, kolik poškozených řádků dekodér snese, než to vzdá, s výchozí hodnotou 0
HotPDF čte tohle všechno ze slovníku obrázku, když zavoláte THotPDF.ExtractLoadedImage nebo THotPDF.DecodeLoadedImageStream. Jedna záměrná odchylka stojí za znalost: když Columns chybí, HotPDF sáhne po Width obrázku místo výchozích 1728 z normy a když chybí Rows, použije Height. Producent, který vynechá Columns u skenu A4 o 2480 pixelech, se plete, ale děje se to dost často na to, aby důvěra v geometrii obrázku dala použitelnou stránku tam, kde doslovná výchozí hodnota dá nesmysl
Proč nelze 000000 brát jako konec bloku?
Ve dvourozměrných řádcích je šest úvodních nulových bitů dvojznačných a dekodér, který je čte jako začátek EOFB, si platný vstup do nekomprimovaného módu přečte jako konec obrázku. Group 4 nemá mezi řádky žádné EOLy; jediné kódy plné nul jsou EOFB, což jsou dvě EOLy za sebou, a rozšiřující kódy z ITU-T T.4 a T.6. Oba začínají 000000 a sedmý bit je místo, kde se rozejdou
CCITT dekodér HotPDF rozluští prefix bit po bitu. Sedmý bit 1 znamená, že další tři bity jsou rozšiřující kód, celý vzorek je 0000001xxx. Do nekomprimovaného módu vstupuje jen xxx = 111; jakákoli jiná hodnota rozšíření se bere jako poškozená, protože faxový filtr PDF ostatní nepotřebuje. Sedmý bit 0 může jen pokračovat v EOLu, takže dekodér pak vyžaduje čtyři další nulové bity a závěrečnou jedničku, celkem jedenáct nul a jedna jednička, než ohlásí EOFB. Cokoli kratšího nebo delšího je chyba, ne shovívavý konec obrázku
Shovívavá verze láká, protože celé třídě rozbitých souborů dá vypadat v pořádku: dekódování prostě skončí dřív a dostanete krátký obrázek bez výjimky. Je to ale přesně ta chyba, která usekne legitimní faxy na prvním úseku nekomprimovaných dat, což je u skenovaného formuláře obvykle hustý podpis nebo razítko. HotPDF volí přísné čtení, takže krátký obrázek znamená, že encoder tam skutečně skončil
Jak nekomprimovaný mód kóduje pixely?
Nekomprimovaný mód vystřídá kódování běhů (run-length) doslovnými pixelovými vzorky, každé kódové slovo nese nejvýš pět pixelů, dokud výstupní kód nepřepne zpátky. Jednorozměrné řádky do něj vstupují 12bitovým kódem 000000001111; dvourozměrné řádky používají výše popsané 10bitové rozšíření 0000001111. Encodery na něj přecházejí u polotónově podobných oblastí, kde by běhy jednoho či dvou pixelů stály víc než samotné pixely
Uvnitř módu se kódová slova počítají podle úvodních nul. Nula až čtyři nuly následované jedničkou znamenají tolik bílých pixelů a pak jeden černý. Pět nul následovaných jedničkou znamená pět bílých pixelů a nic víc. Šest až deset nul následovaných jedničkou jsou výstupní kódy: nuly nad šest vypustí až čtyři další bílé pixely a jediný bit za výstupním kódem, tag bit, nastaví barvu dalšího běhu, když se obyčejné kódování běhů obnoví
{ Kódová slova nekomprimovaného módu (ITU-T T.4 / T.6)
W = white pixel, B = black pixel, T = tag bit
1 B
01 W B
001 W W B
0001 W W W B
00001 W W W W B
000001 W W W W W
0000001T exit, then resume runs with color T
00000001T W, exit
000000001T W W, exit
0000000001T W W W, exit
00000000001T W W W W, exit
more than ten zeros: malformed }
Tag bit je ten jemný detail. Kódování běhů je stavové: 1D řádek se střídá bílé a černé běhy a 2D řádek sleduje barvu aktuálního měnícího se elementu a0. Po výstupním kódu musí dekodér pokračovat barvou, kterou tag jmenuje, a ta může, ale nemusí, odpovídat poslednímu doslovnému pixelu. Dekodér HotPDF zaznamená změnu barvy na aktuální pozici, když se tag liší od barvy, kterou sledoval, takže další běh začíná se správnou barvou. Ignorujte tag a zbytek řádku vyleze s přehozenými barvami a ve 2D módu každý následující řádek zdědí škodu přes svou referenční linku
Co DamagedRowsBeforeError obnoví a co ne?
DamagedRowsBeforeError dovolí HotPDF vyměnit poškozený řádek a pokračovat, ale jen když je K 0 nebo kladné a EndOfLine je true. Ty dvě podmínky nejsou libovolné. Obnova potřebuje způsob, jak najít, kde další řádek začíná, jakmile bity přestávají dávat smysl, a jediným spolehlivým orientačním bodem v CCITT toku je kód EOL. Data Group 4 nemají žádné EOLy a data Group 3 bez povinných EOLů negarantují, že nějaký existuje, takže v obou případech poškozený řádek ukončí dekódování, jakoukoli hodnotu parametr má
Když řádek selže, dekodér HotPDF skenuje dopředu k dalšímu EOLu, běhu nejméně jedenácti nulových bitů následovaných jedničkou, a dosadí náhradní řádek. První poškozený řádek po dobrém se nahradí kopií předchozího řádku, což je na skenované textové stránce obvykle tak blízko, že je to neviditelné. Poškozený řádek hned za jiným poškozeným řádkem se místo toho nahradí celobílým řádkem, protože kopie kopie by rozmázla jednu řádku textu do vysokého černého pruhu. Náhradník se pak stane referenční linkou pro další 2D řádek, což udrží smíšený tok po opravě dekódovatelný
Dvě hranice se dají snadno přehlédnout. Rozpočet je kumulativní přes celý obrázek, ne na běh špatných řádků, takže DamagedRowsBeforeError 3 snese tři poškozené řádky celkem a čtvrtý dekódování zruší. A neúspěšné dekódování je v HotPDF všechno nebo nic: THotPDF.ExtractLoadedImage vrátí nil místo bitmapy s roztřepeným spodním okrajem. THPDFLoadedImageInfo.Decodable říká, že filtr je podporovaný, ne že tento konkrétní tok je neporušený, takže bitmapu vždy kontrolujte na nil
Proč sken musí skončit na začátku EOLu
Resynchronizační sken nechá čtečku na prvním nulovém bitu EOLu, ne za ním. Dekodér má jednu rámovací smyčku, která konzumuje EOLy, kontroluje dvojitý EOL ukončující blok a čte tag bit smíšeného módu. Pokud obnovovací sken EOL spolkne, ta smyčka uvidí řádek bez EOLu, označí ho taky za poškozený a spálí druhou jednotku rozpočtu, zatímco zahodí dobrý řádek. Hůř, přeskočený tag bit znamená, že se 2D řádek dekóduje jako 1D nebo naopak. Táž pravidla platí, když 1D dekodér běhů narazí na nečekaný EOL uprostřed řádku: přetočí se tam, kde kód běhu začínal, takže značka pro obnovovací sken zůstane na místě
Inventura CCITT obrázků v načteném PDF
Než důvěřujete dávce archivovaných faxů, vyliste, které obrázky používají CCITTFaxDecode a u kterých se dekódování nepovedlo. THotPDF.GetLoadedImageInfo vám dá geometrii a název filtru bez dekódování čehokoli a THotPDF.ExtractLoadedImage prozradí, jestli tok přežil. O širším obrazu toho, jak dispečer obrázků směruje každý filtr, píše průvodce extrakcí načtených PDF obrázků a jejich dekódovacích filtrů
var
Pdf: THotPDF;
Info: THPDFLoadedImageInfo;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('fax-archive.pdf', '') <= 0 then
Exit;
for I := 0 to Pdf.GetLoadedImageCount - 1 do
begin
if not Pdf.GetLoadedImageInfo(I, Info) then
Continue;
if Pos('CCITT', UpperCase(String(Info.Filter))) = 0 then
Continue;
Bmp := Pdf.ExtractLoadedImage(I); // nil, když je tok moc poškozený
try
if Bmp = nil then
Writeln(Format('obj %d: %dx%d CCITT failed to decode',
[Info.ObjectNumber, Info.Width, Info.Height]))
else
Bmp.SaveToFile(Format('fax_%d.bmp', [I]));
finally
Bmp.Free; // bitmapu vlastní volající
end;
end;
finally
Pdf.Free;
end;
end;
U neúspěchů si schovejte důkazy. THotPDF.ExtractLoadedImageToStream ve výchozím módu lismEncoded zapíše uložené bajty toku netknuté, což je to, co chcete podat druhému dekodéru, zabalit do kontejneru TIFF k prohlídce nebo přiložit k bug reportu. Překódování poškozeného faxu jen zakryje, kde škoda byla
procedure SaveRawFax(Pdf: THotPDF; ImageIndex: Integer; const FileName: string);
var
Fs: TFileStream;
begin
Fs := TFileStream.Create(FileName, fmCreate);
try
if not Pdf.ExtractLoadedImageToStream(ImageIndex, Fs, lismEncoded) then
raise Exception.CreateFmt('Image %d has no stream data', [ImageIndex]);
finally
Fs.Free;
end;
end;
Pokud si obrázkovými toky projdete sami přes THotPDF.CollectLoadedImageStreams, předejte každý THPDFStreamObject do THotPDF.DecodeLoadedImageStream a dostanete tutéž CCITT cestu, parametry a pravidla obnovy. Uvolněte vrácený seznam, ale ne streamové objekty v něm — ty zůstávají ve vlastnictví dokumentu
Máte u souborů, které nekontrolujete, zvyšovat DamagedRowsBeforeError?
Ne, ne úpravou PDF a obvykle vůbec ne. Parametr patří producentovi, kterému je jedinému známo, jestli jeho přenosový kanál ztrácí bity. Soubor, který deklaruje DamagedRowsBeforeError 0 a pak obsahuje poškozený řádek, vám říká, že archivní kopie je poškozená, a čestným výsledkem je selhání dekódování, které můžete zalogovat. Potichu záplatované řádky ve smlouvě nebo zdravotním záznamu promění detekovatelnou vadu v věrohodně vypadající obrázek s chybějící řádkou, což je horší výsledek pro každého, kdo se na něj později spolehne
Bezpečnější vzorec pro nedůvěryhodné archivy je nechat dekódování přísné, zapsat si, která čísla objektů selhala, a ty poslat člověku nebo samostatnému dekodéru. Je-li archiv velký a přichází z mnoha zdrojů, omezuje běh dekódování obrázků v samostatném procesu, co nepřátelský tok dokáže hostitelské aplikaci; článek o izolaci obrazových kodeků při zpracování nedůvěryhodných PDF rozebírá tohle uspořádání a průvodce kompresí JBIG2 bilevel srovnává druhý bilevel filtr, který ve stejných skenovaných archivech potkáte
FAQ
Podporuje HotPDF nekomprimovaný mód CCITT u obrázků Group 4?
Ano. HotPDF pozná 10bitové rozšíření 0000001111 ve dvourozměrných řádcích a 12bitový vstupní kód 000000001111 v jednorozměrných řádcích, dekóduje doslovná kódová slova a respektuje výstupní tag bit, když se kódování běhů obnoví
Proč nemá DamagedRowsBeforeError u mého faxu Group 4 žádný účinek?
Obnova potřebuje k resynchronizaci značky EOL. HotPDF aplikuje DamagedRowsBeforeError jen tehdy, když je K 0 nebo kladné a EndOfLine je true; data Group 4 (záporné K) nemají žádné EOLy, takže jakýkoli poškozený řádek dekódování ukončí
Co vrátí ExtractLoadedImage pro poškozený CCITT obrázek?
THotPDF.ExtractLoadedImage vrátí nil, když škoda přesahuje to, co dovoluje DamagedRowsBeforeError, nebo se nedá obnovit vůbec. Nikdy nevrací částečnou bitmapu, takže testujte na nil a pro uchování syrových bajtů použijte ExtractLoadedImageToStream
HotPDF zpracovává obrázky CCITT, JBIG2, JPEG, JPEG 2000 a Flate v načtených PDF z obyčejného kódu Delphi a C++Builder, bez externí DLL pro faxovou cestu. Detaily a edice najdete na stránce komponenty HotPDF Delphi PDF