HotPDF, il componente PDF nativo per Delphi, decodifica le immagini CCITTFaxDecode con supporto completo per l'estensione uncompressed-mode T.4/T.6 e per il recupero delle righe danneggiate guidato da DamagedRowsBeforeError. La modalità non compressa si entra solo con il codice di estensione che termina in 111, e le righe danneggiate vengono riparate solo per dati Group 3 con EndOfLine attivo. Tutto il resto fallisce in modo pulito invece di produrre una pagina inclinata
Le immagini codificate fax sono la coda lunga della decodifica delle immagini PDF. La maggior parte degli stream CCITT che incontrate esce da un driver di scanner o da una pipeline di acquisizione documenti che emette solo Group 4 semplice, e un decoder che gestisce il caso comune supera qualunque test gli lanciate contro. Poi arriva un archivio da un fax server di quindici anni fa, una pagina si renderizza come rumore diagonale dalla riga 400 in giù, e scoprite che le due parti delle raccomandazioni ITU-T che nessuno implementa sono esattamente le due parti che quel file usa. Questo articolo percorre quelle due parti così come le gestisce HotPDF, e i punti in cui un decoder può andare storto pur sembrando corretto
Quali parametri CCITTFaxDecode cambiano davvero il bit stream?
Quattro voci del dizionario DecodeParms cambiano il modo in cui il decoder incornicia il bit stream: K, EndOfLine, EncodedByteAlign e DamagedRowsBeforeError. ISO 32000-1 §7.4.6 (Tabella 11) le definisce, e sbagliarne una sola sposta ogni riga dopo la prima discrepanza. BlackIs1, Columns, Rows e EndOfBlock contano anch'esse, ma agiscono su polarità, geometria e punto di arresto della decodifica, non su come si trova un confine di riga
Ksceglie lo schema di codifica: negativo significa Group 4 puramente bidimensionale (T.6), zero significa Modified Huffman unidimensionale (Group 3 1D), e positivo significa Group 3 2D misto, dove un bit tag dopo ogni EOL dice se la riga successiva è 1D o 2DEndOfLinedice che ogni riga Group 3 è preceduta da un codice EOL, undici bit a zero seguiti da un unoEncodedByteAligndice che ogni riga codificata inizia su un confine di byte, quindi il decoder deve scartare i bit di riempimento prima di leggerlaDamagedRowsBeforeErrorè quante righe corrotte il decoder può tollerare prima di arrendersi, con un predefinito di 0
HotPDF legge tutti questi valori dal dizionario immagine quando chiamate THotPDF.ExtractLoadedImage o THotPDF.DecodeLoadedImageStream. Una deviazione deliberata merita di essere conosciuta: quando Columns manca, HotPDF ripiega sulla Width dell'immagine invece che sul predefinito di specifica di 1728, e quando Rows manca usa Height. Un produttore che omette Columns su una scansione A4 da 2480 pixel sbaglia, ma è abbastanza comune che fidarsi della geometria dell'immagine dia una pagina usabile dove il predefinito letterale darebbe spazzatura
Perché 000000 non può essere trattato come fine del blocco?
Nelle righe bidimensionali sei bit a zero iniziali sono ambigui, e un decoder che li legge come inizio di EOFB scambierà un valido ingresso in modalità non compressa per la fine dell'immagine. Group 4 non ha EOL fra le righe; gli unici codici ricchi di zeri sono l'EOFB, che sono due EOL di fila, e i codici di estensione di ITU-T T.4 e T.6. Entrambi iniziano con 000000, e il settimo bit è dove divergono
Il decoder CCITT di HotPDF risolve il prefisso bit per bit. Un settimo bit pari a 1 significa che i tre bit successivi sono un codice di estensione, il pattern completo essendo 0000001xxx. Solo xxx = 111 entra in modalità non compressa; qualunque altro valore di estensione è trattato come malformato, dato che un filtro fax PDF non ha uso per gli altri. Un settimo bit pari a 0 può solo continuare un EOL, quindi il decoder richiede allora altri quattro bit a zero e un 1 di chiusura, undici zeri e un uno in totale, prima di riportare EOFB. Qualcosa di più corto o più lungo è un errore, non una fine immagine indulgente
La versione indulgente è tentante perché fa sembrare a posto un'intera classe di file rotti: la decodifica si ferma semplicemente presto e vi ritrovate un'immagine corta senza eccezioni. È anche esattamente il bug che tronca fax legittimi al primo tratto di dati non compressi, che su un modulo scansionato di solito è una firma fitta o un timbro. HotPDF sceglie la lettura severa, quindi un'immagine corta significa che l'encoder ha davvero finito lì
Come codifica i pixel la modalità non compressa?
La modalità non compressa sostituisce la codifica a run-length con pattern di pixel letterali, ogni parola codice portando al massimo cinque pixel, finché un codice di uscita non torna indietro. Le righe unidimensionali vi entrano con il codice a 12 bit 000000001111; le righe bidimensionali usano l'estensione a 10 bit 0000001111 descritta sopra. Gli encoder vi passano per le regioni tipo halftone dove run di uno o due pixel costerebbero più dei pixel stessi
Dentro la modalità, le parole codice si contano dagli zeri iniziali. Da zero a quattro zeri seguiti da un 1 significano altrettanti pixel bianchi e poi un pixel nero. Cinque zeri seguiti da un 1 significano cinque pixel bianchi e nient'altro. Da sei a dieci zeri seguiti da un 1 sono codici di uscita: gli zeri oltre sei emettono fino ad altri quattro pixel bianchi, e il bit singolo dopo il codice di uscita, il bit tag, imposta il colore del prossimo run quando riprende la codifica a run ordinaria
{ Parole codice della modalità non compressa (ITU-T T.4 / T.6)
W = pixel bianco, B = pixel nero, T = bit tag
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 uscita, poi i run riprendono col colore T
00000001T W, uscita
000000001T W W, uscita
0000000001T W W W, uscita
00000000001T W W W W, uscita
più di dieci zeri: malformato }
Il bit tag è la parte sottile. La codifica a run mantiene stato: una riga 1D alterna run bianchi e neri, e una riga 2D traccia il colore dell'elemento di cambiamento corrente a0. Dopo il codice di uscita, il decoder deve riprendere con il colore che il tag nomina, che può corrispondere o no all'ultimo pixel letterale. Il decoder di HotPDF registra un cambio di colore alla posizione corrente quando il tag differisce dal colore che stava tracciando, così il prossimo run parte dal colore giusto. Ignorate il tag e il resto della riga esce con i colori invertiti, e in modalità 2D ogni riga successiva eredita il danno attraverso la sua riga di riferimento
Cosa recupera DamagedRowsBeforeError, e cosa no?
DamagedRowsBeforeError permette a HotPDF di sostituire una riga corrotta e continuare, ma solo quando K è 0 o positivo e EndOfLine è true. Quelle due condizioni non sono arbitrarie. Il recupero ha bisogno di un modo per trovare dove inizia la riga successiva dopo che i bit hanno smesso di avere senso, e l'unico punto di riferimento affidabile in uno stream CCITT è il codice EOL. I dati Group 4 non hanno EOL, e i dati Group 3 senza EOL obbligatori non danno garanzia che esista, quindi in entrambi i casi una riga danneggiata termina la decodifica a prescindere dal valore del parametro
Quando una riga fallisce, il decoder di HotPDF scansiona in avanti fino al prossimo EOL, una sequenza di almeno undici bit a zero seguiti da un uno, e sostituisce una riga di ricambio. La prima riga danneggiata dopo una buona viene sostituita con una copia della riga precedente, che su una pagina di testo scansionato di solito è abbastanza vicina da risultare invisibile. Una riga danneggiata che segue direttamente un'altra riga danneggiata viene invece sostituita con una riga tutta bianca, perché copiare una copia spalmerebbe una riga di testo in una barra nera alta. Il sostituto diventa poi la riga di riferimento per la prossima riga 2D, che è ciò che mantiene decodificabile uno stream in modalità mista dopo la riparazione
Due confini sono facili da perdere. Il budget è cumulativo su tutta l'immagine, non per sequenza di righe cattive, quindi DamagedRowsBeforeError 3 tollera tre righe danneggiate in totale e la quarta fa fallire la decodifica. E una decodifica fallita in HotPDF è tutto o niente: THotPDF.ExtractLoadedImage restituisce nil invece di una bitmap con il bordo inferiore a strappi. THPDFLoadedImageInfo.Decodable riporta che il filtro è supportato, non che questo stream particolare è intatto, quindi controllate sempre la bitmap per nil
Perché la scansione deve fermarsi all'inizio dell'EOL
La scansione di risincronizzazione lascia il reader al primo bit a zero dell'EOL, non dopo. Il decoder ha un unico loop di incorniciatura che consuma gli EOL, controlla il doppio EOL che chiude il blocco e legge il bit tag della modalità mista. Se la scansione di recupero ingoia l'EOL stesso, quel loop vede una riga senza EOL, la tratta come danneggiata a sua volta e brucia una seconda unità di budget scartando una riga buona. Peggio, un bit tag saltato significa che una riga 2D viene decodificata come 1D o viceversa. La stessa regola vale quando un decoder di run 1D incontra un EOL inatteso a metà riga: riavvolge al punto in cui il codice di run era iniziato, così il marcatore resta lì per la scansione di recupero
Fare l'inventario delle immagini CCITT in un PDF caricato
Prima di fidarvi di un lotto di fax archiviati, elencate quali immagini usano CCITTFaxDecode e quali non riescono a decodificarsi. THotPDF.GetLoadedImageInfo vi dà geometria e nome del filtro senza decodificare nulla, e THotPDF.ExtractLoadedImage vi dice se lo stream è sopravvissuto. Per il quadro più ampio di come il dispatcher delle immagini instrada ogni filtro, vedere la guida a estrazione delle immagini PDF caricate e dei loro filtri di decodifica
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 quando lo stream è troppo danneggiato
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; // la bitmap appartiene a chi chiama
end;
end;
finally
Pdf.Free;
end;
end;
Per i fallimenti, conservate la prova. THotPDF.ExtractLoadedImageToStream con la modalità predefinita lismEncoded scrive i byte dello stream memorizzato intatti, che è ciò che volete passare a un secondo decoder, avvolgere in un contenitore TIFF per ispezione o allegare a un bug report. Ricodificare un fax danneggiato non fa che nascondere dov'era il danno
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;
Se percorrete voi gli stream immagine tramite THotPDF.CollectLoadedImageStreams, passate ogni THPDFStreamObject a THotPDF.DecodeLoadedImageStream e otterrete lo stesso percorso CCITT, gli stessi parametri e le stesse regole di recupero. Liberate l'elenco restituito ma non gli oggetti stream dentro, che restano di proprietà del documento
Conviene alzare DamagedRowsBeforeError su file che non controllate?
No, non modificando il PDF, e di solito per niente. Il parametro appartiene al produttore, che è l'unica parte che sa se il suo canale di trasmissione perde bit. Un file che dichiara DamagedRowsBeforeError 0 e poi contiene una riga danneggiata vi sta dicendo che la copia d'archivio è corrotta, e il risultato onesto è un fallimento di decodifica che potete registrare. Rattoppare in silenzio le righe di un contratto o di una cartella clinica trasforma un difetto rilevabile in un'immagine plausibile con una riga mancante, che è un esito peggiore per chiunque vi farà affidamento più tardi
Il pattern più sicuro per archivi non fidati è tenere la decodifica severa, registrare quali numeri di oggetto hanno fallito e instradarli a un essere umano o a un decoder separato. Se l'archivio è grande e arriva da molte fonti, far girare la decodifica delle immagini fuori processo limita ciò che uno stream ostile può fare all'applicazione ospite; l'articolo su isolamento dei codec immagine quando si elaborano PDF non fidati copre quel setup, e la guida alla compressione bilevel JBIG2 confronta l'altro filtro bilevel che troverete negli stessi archivi scansionati
FAQ
HotPDF supporta la modalità CCITT non compressa nelle immagini Group 4?
Sì. HotPDF riconosce l'estensione a 10 bit 0000001111 nelle righe bidimensionali e il codice d'ingresso a 12 bit 000000001111 nelle righe unidimensionali, decodifica le parole codice letterali e onora il bit tag di uscita quando riprende la codifica a run
Perché DamagedRowsBeforeError non ha effetto sul mio fax Group 4?
Il recupero richiede marcatori EOL per risincronizzarsi. HotPDF applica DamagedRowsBeforeError solo quando K è 0 o positivo e EndOfLine è true; i dati Group 4 (K negativo) non hanno EOL, quindi qualunque riga danneggiata termina la decodifica
Cosa restituisce ExtractLoadedImage per un'immagine CCITT danneggiata?
THotPDF.ExtractLoadedImage restituisce nil quando il danno supera ciò che DamagedRowsBeforeError consente o non è recuperabile per niente. Non restituisce mai una bitmap parziale, quindi testate per nil e usate ExtractLoadedImageToStream per conservare i byte grezzi
HotPDF gestisce immagini CCITT, JBIG2, JPEG, JPEG 2000 e Flate in PDF caricati da semplice codice Delphi e C++Builder, senza DLL esterne per il percorso fax. Dettagli ed edizioni sono sulla pagina del componente HotPDF Delphi PDF