HotPDF, den native Delphi PDF-komponent, afkoder CCITTFaxDecode-billeder med fuld understøttelse af T.4/T.6 uncompressed mode-udvidelsen og af gendannelse af skadede rækker styret af DamagedRowsBeforeError. Uncompressed mode indtrædes kun via udvidelseskoden sluttende på 111, og skadede rækker repareres kun for Group 3-data med EndOfLine sat. Alt andet fejler rent i stedet for at frembringe en skæv side
Fax-kodede billeder er den lange hale af PDF-billedafkodning. De fleste CCITT-strømme, man møder, kommer fra en scannerdriver eller en dokument-capture-pipeline, der kun udsender almindelig Group 4, og en afkoder, der håndterer det almindelige tilfælde, består alle de tests, man kaster efter den. Så ankommer et arkiv fra en femten år gammel faxserver, en side renderes som diagonalt støj fra række 400 og ned, og man opdager, at de to dele af ITU-T-anbefalingerne, som ingen implementerer, er præcis de to dele, den fil bruger. Denne artikel gennemgår de to dele, som HotPDF håndterer dem, og de steder, hvor en afkoder kan tage fejl og stadig se korrekt ud
Hvilke CCITTFaxDecode-parametre ændrer rent faktisk bitstrømmen?
Fire poster i DecodeParms-dictionaryen ændrer, hvordan afkoderen indrammer bitstrømmen: K, EndOfLine, EncodedByteAlign og DamagedRowsBeforeError. ISO 32000-1 §7.4.6 (Table 11) definerer dem, og at få én af dem forkert forskubber hver række efter det første mismatch. BlackIs1, Columns, Rows og EndOfBlock betyder også noget, men de påvirker polaritet, geometri og hvor afkodningen stopper snarere end hvordan en rækkegrænse findes
Kvælger kodningsskemaet: negativ betyder ren todimensional Group 4 (T.6), nul betyder endimensional Modified Huffman (Group 3 1D), og positiv betyder blandet Group 3 2D, hvor en tag-bit efter hver EOL siger, om næste række er 1D eller 2DEndOfLinesiger, at hver Group 3-række indledes med en EOL-kode, elleve nul-bits efterfulgt af en enerEncodedByteAlignsiger, at hver kodet række starter på en bytegrænse, så afkoderen skal kassere padding-bits, før den læser denDamagedRowsBeforeErrorer, hvor mange korrupte rækker afkoderen må tolerere, før den giver op, med standard 0
HotPDF læser alle disse fra billed-dictionaryen, når man kalder THotPDF.ExtractLoadedImage eller THotPDF.DecodeLoadedImageStream. Én bevidst afvigelse er værd at kende: mangler Columns, falder HotPDF tilbage på billedets Width frem for spec-standardens 1728, og mangler Rows, bruger den Height. En producer, der udelader Columns på et 2480-pixel A4-scan, tager fejl, men det er almindeligt nok til, at det at stole på billedets geometri giver en brugbar side, hvor den bogstavelige standard ville give skrald
Hvorfor kan 000000 ikke behandles som slutningen på blokken?
I todimensionale rækker er seks indledende nul-bits tvetydige, og en afkoder, der læser dem som begyndelsen på EOFB, vil misfortolke en gyldig uncompressed mode-indgang som slutningen på billedet. Group 4 har ingen EOL'er mellem rækker; de eneste noltunge koder er EOFB, som er to EOL'er ryg mod ryg, og udvidelseskoderne fra ITU-T T.4 og T.6. Begge begynder med 000000, og den syvende bit er, hvor de adskiller sig
HotPDF CCITT-afkoderen opløser præfikset bit for bit. En syvende bit på 1 betyder, at de næste tre bits er en udvidelseskode, hele mønsteret værende 0000001xxx. Kun xxx = 111 fører ind i uncompressed mode; enhver anden udvidelsesværdi behandles som misdannet, da et PDF-faxfilter ikke har brug for de andre. En syvende bit på 0 kan kun fortsætte en EOL, så afkoderen kræver derefter fire flere nul-bits og en afsluttende 1, i alt elleve nuller og en ener, før den rapporterer EOFB. Alt kortere eller længere er en fejl, ikke en efterladende slut-på-billede
Den efterladende version er fristende, for den får en hel klasse af ødelagte filer til at se fine ud: afkodningen stopper blot tidligt, og man får et kort billede uden exception. Den er også præcis den bug, der afkorter legitime faxer ved den første strækning af uncompressed data, hvilket på et scannet formular typisk er en tæt underskrift eller et stempel. HotPDF vælger den strikte læsning, så et kort billede betyder, at encoderen virkelig sluttede dér
Hvordan koder uncompressed mode pixels?
Uncompressed mode erstatter run-length-kodning med literale pixelmønstre, hvor hvert kodeword bærer højst fem pixels, indtil en exit-kode skifter tilbage. Endimensionale rækker træder ind i den med 12-bit-koden 000000001111; todimensionale rækker bruger 10-bit-udvidelsen 0000001111 beskrevet ovenfor. Encodere skifter til den for halvtone-agtige regioner, hvor run-længder på én eller to pixels ville koste mere end pixelsne selv
Inde i moden tælles kodewordene efter deres indledende nuller. Nul til fire nuller efterfulgt af en 1 betyder så mange hvide pixels og derefter én sort pixel. Fem nuller efterfulgt af en 1 betyder fem hvide pixels og intet andet. Seks til ti nuller efterfulgt af en 1 er exit-koder: nullerne ud over seks udsender op til fire flere hvide pixels, og den enkelte bit efter exit-koden, tag-bitten, sætter farven på det næste run, når almindelig run-kodning genoptages
{ Uncompressed mode-kodewords (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-bitten er den subtile del. Run-kodning har tilstand: en 1D-række skifter mellem hvide og sorte runs, og en 2D-række sporer farven på det aktuelle skiftende element a0. Efter exit-koden skal afkoderen genoptage med den farve, tagen nævner, hvilket måske matcher den sidste literale pixel, måske ikke. HotPDF-afkoderen registrerer en farveændring på den aktuelle position, når tagen afviger fra den farve, den sporede, så næste run starter med den rigtige farve. Ignorerer man tagen, kommer resten af rækken ud farve-inverteret, og i 2D-mode arver hver efterfølgende række skaden gennem sin referencelinje
Hvad gendanner DamagedRowsBeforeError, og hvad gør den ikke?
DamagedRowsBeforeError lader HotPDF erstatte en korrupt række og fortsætte, men kun når K er 0 eller positiv, og EndOfLine er true. De to betingelser er ikke vilkårlige. Gendannelse har brug for en måde at finde ud af, hvor næste række starter, efter bitsene holder op med at give mening, og det eneste pålidelige vartegn i en CCITT-strøm er EOL-koden. Group 4-data har ingen EOL'er, og Group 3-data uden obligatoriske EOL'er giver ingen garanti for, at der findes én, så i begge tilfælde afslutter en skadet række afkodningen uanset parameterværdien
Når en række fejler, scanner HotPDF-afkoderen frem til næste EOL, et run på mindst elleve nul-bits efterfulgt af en ener, og indsætter en erstatningsrække. Den første skadede række efter en god erstattes af en kopi af den forrige række, hvilket på en scannet tekstside normalt er tæt nok på til at være usynligt. En skadet række, der direkte følger efter en anden skadet række, erstattes i stedet med en helhvid række, for at kopiere en kopi ville smøre én tekstlinje ud i en høj sort bjælke. Erstatningen bliver så referencelinjen for næste 2D-række, hvilket er det, der holder en mixed mode-strøm afkodbar efter reparationen
To grænser er lette at overse. Budgettet er kumulativt over hele billedet, ikke pr. række af dårlige rækker, så DamagedRowsBeforeError 3 tolererer tre skadede rækker i alt, og den fjerde fikser afkodningen. Og en fejlet afkodning i HotPDF er alt eller intet: THotPDF.ExtractLoadedImage returnerer nil frem for en bitmap med en laset bundkant. THPDFLoadedImageInfo.Decodable rapporterer, at filteret er understøttet, ikke at netop denne strøm er intakt, så tjek altid bitmapen for nil
Hvorfor scanningen skal stoppe ved EOL'ens begyndelse
Resynkroniseringsscanningen efterlader læseren ved den første nul-bit i EOL'en, ikke efter den. Afkoderen har én framing-løkke, der indtager EOL'er, tjekker den dobbelte EOL, der afslutter blokken, og læser mixed mode-tag-bitten. Sluger gendannelsesscanningen EOL'en selv, ser den løkke en række uden EOL, behandler den også som skadet og brænder en ekstra budgetenhed af, mens den smider en god række væk. Værre er det, at en sprunget tag-bit betyder, at en 2D-række afkodes som 1D eller omvendt. Samme regel gælder, når en 1D run-afkoder rammer en uventet EOL midt i en række: den spoler tilbage til, hvor run-koden startede, så markøren stadig er der til gendannelsesscanningen
Inventar af CCITT-billeder i et indlæst PDF
Før man stoler på en bunke arkiverede faxer, skal man liste, hvilke billeder bruger CCITTFaxDecode, og hvilke der fejler i afkodning. THotPDF.GetLoadedImageInfo giver geometri og filternavnet uden at afkode noget, og THotPDF.ExtractLoadedImage fortæller, om strømmen overlevede. For det bredere billede af, hvordan image dispatcheren ruter hvert filter, se guiden til udtræk af indlæste PDF-billeder og deres decode-filtre
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, når strømmen er for skadet
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; // kalderen ejer bitmapen
end;
end;
finally
Pdf.Free;
end;
end;
Til fejlerne: gem beviset. THotPDF.ExtractLoadedImageToStream med standard lismEncoded-mode skriver de lagrede strøm-bytes urørt, hvilket er det, man vil give til en anden afkoder, pakke ind i en TIFF-container til inspektion eller vedhæfte til en bugrapport. At re-encode en skadet fax skjuler blot, hvor skaden var
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;
Gennemgår man billedstrømme selv gennem THotPDF.CollectLoadedImageStreams, så giv hver THPDFStreamObject til THotPDF.DecodeLoadedImageStream, og man får samme CCITT-vej, parametre og gendannelsesregler. Frigiv den returnerede liste, men ikke strøm-objekterne i den, som forbliver ejet af dokumentet
Bør man hæve DamagedRowsBeforeError på filer, man ikke kontrollerer?
Nej, ikke ved at redigere PDF'en, og som regel slet ikke. Parameteren tilhører producenten, som er den eneste part, der ved, om dens transmissionskanal taber bits. En fil, der erklærer DamagedRowsBeforeError 0 og derefter indeholder en skadet række, fortæller en, at arkivkopien er korrupt, og det ærlige resultat er en afkodningsfejl, man kan logge. At lappe rækker i stilhed på en kontrakt eller en journal gør en detekterbar defekt til et plausibelt udseende billede med en manglende linje, hvilket er et værre udfald for enhver, der senere stoler på det
Det sikrere mønster til utroværdige arkiver er at holde afkodningen strikt, notere, hvilke objektnumre der fejlede, og sende dem videre til et menneske eller en separat afkoder. Er arkivet stort og kommer fra mange kilder, begrænser det at køre billedafkodning out of process, hvad en fjendtlig strøm kan gøre mod værtsprogrammet; artiklen om at isolere image codecs ved behandling af utroværdige PDF'er dækker den opsætning, og JBIG2 bilevel compression-guiden sammenligner det andet bilevel-filter, man finder i de samme scannede arkiver
FAQ
Understøtter HotPDF CCITT uncompressed mode i Group 4-billeder?
Ja. HotPDF genkender 10-bit-udvidelsen 0000001111 i todimensionale rækker og 12-bit-indgangskoden 000000001111 i endimensionale rækker, afkoder de literale kodewords og respekterer exit-tag-bitten, når run-kodning genoptages
Hvorfor har DamagedRowsBeforeError ingen effekt på min Group 4-fax?
Gendannelse kræver EOL-markører for at resynkronisere. HotPDF anvender DamagedRowsBeforeError kun, når K er 0 eller positiv, og EndOfLine er true; Group 4-data (K negativ) har ingen EOL'er, så enhver skadet række afslutter afkodningen
Hvad returnerer ExtractLoadedImage for et skadet CCITT-billede?
THotPDF.ExtractLoadedImage returnerer nil, når skaden overstiger, hvad DamagedRowsBeforeError tillader, eller slet ikke kan gendannes. Den returnerer aldrig en delvis bitmap, så test for nil og brug ExtractLoadedImageToStream til at gemme de rå bytes
HotPDF håndterer CCITT-, JBIG2-, JPEG-, JPEG 2000- og Flate-billeder i indlæste PDF'er fra almindelig Delphi- og C++Builder-kode, uden ekstern DLL til fax-stien. Detaljer og udgaver finder man på HotPDF Delphi PDF component-siden