Ένα σαρωμένο συμβόλαιο πενήντα σελίδων επαναλαμβάνει το ίδιο αλφάβητο σε κάθε σελίδα, αλλά ένας κωδικοποιητής JBIG2 που χτίζει ένα λεξικό συμβόλων ανά εικόνα επανεκπαιδεύει εκείνο το αλφάβητο πενήντα ξεχωριστές φορές. Το HotPDF, το γηγενές εξάρτημα PDF για Delphi και C++Builder, μπορεί αντ' αυτού να συσσωρεύσει ένα κοινό λεξικό συμβόλων σε ολόκληρο το έγγραφο και να το προάγει σε μία ροή /JBIG2Globals σε επίπεδο εγγράφου, ώστε η δική της ροή JBIG2 κάθε σελίδας απλώς να αναφέρεται σε αναγνωριστικά συμβόλων αντί να αποθηκεύει το δικό της αντίγραφο του αλφαβήτου
Αυτό το κείμενο παραμένει σκόπιμα στενό και καλύπτει μόνο πώς το HotPDF χτίζει αυτή την κοινή χρήση μεταξύ σελίδων εσωτερικά — τα θεμελιώδη του JBIG2, η σύγκριση με CCITT, και οι συμβιβασμοί Lossless έναντι LossyLevel ζουν ήδη στο συνοδευτικό άρθρο για τη γηγενή συμπίεση JBIG2 δύο επιπέδων σε Delphi, που αυτό το κείμενο υποθέτει ότι έχετε διαβάσει
Γιατί η συμπίεση JBIG2 ανά σελίδα εξακολουθεί να επαναλαμβάνει το ίδιο κόστος;
Η απάντηση είναι ότι τίποτα δεν μεταφέρει κατάσταση μεταξύ κλήσεων. Κάθε φορά που ο κωδικοποιητής του HotPDF χτίζει ένα λεξικό συμβόλων για μία εικόνα, εκείνο το λεξικό περιορίζεται σε εκείνη τη μοναδική κλήση AddImage: το πέρασμα ταιριάσματος σχήματος ξεκινά από το μηδέν, κάθε γλύφος στη σελίδα ταξινομείται ως νέος, και τα προκύπτοντα bitmap κωδικοποιούνται αριθμητικά και αποθηκεύονται φρέσκα. Δώστε στον ίδιο κωδικοποιητή πενήντα σελίδες στοιχειοθετημένες στην ίδια γραμματοσειρά και επαναλαμβάνει ευχαρίστως ολόκληρο εκείνο το πέρασμα εκπαίδευσης πενήντα φορές, επειδή από τη δική του οπτική γωνία κάθε σελίδα είναι μια ασύνδετη εικόνα που τυχαίνει να μοιάζει παρόμοια. Το ανά σελίδα UseSymbolDictionary ήδη ξεπερνά κατά πολύ μια επίπεδη κωδικοποίηση γενικής περιοχής σε μία μόνο σελίδα, αλλά κορυφώνεται πολύ πριν το ανώτατο όριο που αφήνει στο τραπέζι μια πραγματική πολυσέλιδη σάρωση
Πώς μοιράζεται το HotPDF ένα μοναδικό λεξικό συμβόλων μεταξύ σελίδων;
Ενεργοποιήστε το AccumulateGlobalsAcrossPages στο THPDFJBIG2Options και το HotPDF κρατά ένα λεξικό συμβόλων ζωντανό στη μνήμη για όλη τη ζωή του εγγράφου αντί να το απορρίπτει μετά από κάθε εικόνα. Οι γλύφοι κάθε επόμενης σελίδας ελέγχονται έναντι εκείνου του τρέχοντος λεξικού πριν κωδικοποιηθεί οτιδήποτε ξανά: ένα σχήμα που ήδη υπάρχει επαναχρησιμοποιείται μέσω του αναγνωριστικού συμβόλου του, και μόνο ένα σχήμα που κανείς δεν έχει ξαναδεί προσαρτάται και κωδικοποιείται στο λεξικό. Η σύγκριση επαναχρησιμοποιεί την ίδια λογική ανοχής που εφαρμόζει το LossyLevel σε μία σελίδα — μια ελαφρώς θορυβώδης σάρωση του ίδιου γράμματος εξακολουθεί να μετράει ως ταίριασμα — οπότε ο συσσωρευτής δεν φουσκώνει σιωπηλά σε μία καταχώριση λεξικού ανά παραλλαγή επιπέδου pixel του ίδιου γλύφου. Η εξαγωγή συμβαίνει πρώτη και τροφοδοτεί εκείνη τη σύγκριση: το HotPDF διατρέχει το bitmap κάθε σελίδας και τραβά συνδεδεμένα σχήματα μέσω flood fill έναντι των μαύρων pixel, την ίδια ιδέα με το να ιχνηλατείτε μελανιές μελανιού με το χέρι, και είναι εκείνα τα εξαγόμενα σχήματα, όχι ακατέργαστα μπλοκ pixel, που συγκρίνονται με το τρέχον λεξικό
Πώς βρίσκεται το κοινό λεξικό μέσα σε μια ροή /JBIG2Globals
Το συσσωρευμένο λεξικό γράφεται ως ένα τμήμα λεξικού συμβόλων μέσα στη ροή /JBIG2Globals, με σταθερό αριθμό τμήματος ώστε κάθε σελίδα να μπορεί να δείχνει στον ίδιο στόχο. Μέσα στην ενσωματωμένη οργάνωση JBIG2 που ορίζει το ISO 32000-1 §7.4.7, ένα τμήμα text-region μπορεί να ονομάσει ένα άλλο τμήμα ως πηγή συμβόλων του μέσω του πεδίου αναφερόμενου-τμήματος στην κεφαλίδα τμήματος, και αυτός είναι ακριβώς ο μηχανισμός στον οποίο στηρίζεται το HotPDF: η ροή globals φέρει το ένα μεγάλο λεξικό συμβόλων, και η δική της ροή JBIG2 κάθε σελίδας συρρικνώνεται σε ένα τμήμα page-info συν ένα τμήμα text-region του οποίου η λίστα αναφερόμενων-τμημάτων δείχνει πίσω στο τμήμα globals. Ό,τι ήταν κάποτε ένα αυτοτελές bitstream ανά σελίδα γίνεται μια σύντομη λίστα θέσεων και αναγνωριστικών συμβόλων, και κάθε σελίδα χτισμένη με αυτόν τον τρόπο αναφέρεται στο ίδιο έμμεσο αντικείμενο /JBIG2Globals αντί σε αντίγραφό του. Η δική του κάλυψη παλινδρόμησης του HotPDF ελέγχει ακριβώς αυτό: κωδικοποιήστε ένα σύντομο έγγραφο όπου κάθε σελίδα έχει διαφορετική διάταξη γλύφων, επαναφορτώστε το, και μετρήστε πόσες ξεχωριστές αναφορές αντικειμένου /JBIG2Globals εμφανίζονται στο αρχείο — ένα έγγραφο, μία αναφορά αντικειμένου, όσες σελίδες κι αν συνέβαλαν σύμβολα σε αυτό
Ενεργοποίηση της συσσώρευσης λεξικού συμβόλων μεταξύ σελίδων
Ο διακόπτης βρίσκεται στην ίδια εγγραφή επιλογών που καλύπτεται στο συνοδευτικό άρθρο, και χρειάζεται τέσσερις ρυθμίσεις να συμφωνούν μεταξύ τους προτού η συσσώρευση πράγματι ενεργοποιηθεί
var
Pdf: THotPDF;
Bmp: TBitmap;
PageIdx, ImgIdx: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.JBIG2Options.Lossless := True;
Pdf.JBIG2Options.UseSymbolDictionary := True;
Pdf.JBIG2Options.UseGlobalSegments := True;
Pdf.JBIG2Options.AccumulateGlobalsAcrossPages := True; // opt-in, default False
Pdf.JBIG2Options.UseExternalEncoder := False; // accumulation needs the native path
Pdf.JBIG2Options.UseNativeArithmeticFallback := True;
Pdf.BeginDoc;
for PageIdx := 0 to ScannedPages.Count - 1 do
begin
if PageIdx > 0 then
Pdf.AddPage;
Bmp := ScannedPages[PageIdx]; // 1-bit TBitmap for this page
ImgIdx := Pdf.AddImage(Bmp, icJBIG2);
Pdf.CurrentPage.ShowImage(ImgIdx, 0, 0, Bmp.Width, Bmp.Height, 0);
end;
Pdf.EndDoc; // the shared /JBIG2Globals stream is finalized here
finally
Pdf.Free;
end;
end;
Αυτό το ζευγάρωμα δεν είναι προαιρετική διακόσμηση. Η αρμογή εξωτερικού κωδικοποιητή που περιγράφεται στο άρθρο συμπίεσης δύο επιπέδων — αυτή που καταχωρείτε μέσω RegisterJBIG2EncoderBackend για λόγους παραγωγικών αναλογιών — είναι χτισμένη γύρω από κωδικοποίηση ανά εικόνα, και οι δικές του επιδείξεις συσσώρευσης και τα τεστ παλινδρόμησης του HotPDF πάντα ζευγαρώνουν το AccumulateGlobalsAcrossPages με UseExternalEncoder := False. Αντιμετωπίστε το ως σκληρή απαίτηση αντί για πρόταση: η κοινή χρήση μεταξύ σελίδων είναι δυνατότητα γηγενούς κωδικοποιητή, και μια καταχωρημένη εξωτερική αρμογή απλά δεν είναι μέρος της διαδρομής που χτίζει το κοινό λεξικό
Πόσο μικρότερη γίνεται πράγματι μια πολυσέλιδη σάρωση;
Η ειλικρινής απάντηση ξεκινά με το τι δεν κούνησε τη βελόνα πρώτα. Μια παλαιότερη έκδοση πρόσθεσε μια προσωρινή μνήμη με διεύθυνση περιεχομένου για ροές /JBIG2Globals — μια αναζήτηση με κλειδί ένα 64-bit hash FNV-1a των byte της ροής, ώστε δύο εικόνες που έτυχε να παράγουν πανομοιότυπα byte δεδομένα globals να μπορούν να μοιραστούν ένα αντικείμενο PDF. Μετρημένη έναντι πραγματικής εξόδου, αυτή η προσωρινή μνήμη μόλις που βοήθησε, επειδή η ήδη υπάρχουσα ανίχνευση διπλότυπων ολόκληρης εικόνας του HotPDF ήδη κατέρρεε πανομοιότυπες byte εικόνες πριν η προσωρινή μνήμη προλάβει ποτέ να τρέξει. Το μάθημα ήταν ότι η αποδιπλασιοποίηση σε επίπεδο ροής αποδίδει μόνο μόλις δύο γνησίως διαφορετικές εικόνες σελίδας μπορούν ακόμη να μοιραστούν ένα αναπτυσσόμενο λεξικό, κάτι που είναι αυτό που παραδίδει η αληθινή συσσώρευση μεταξύ σελίδων
Για εκείνη τη δυσκολότερη περίπτωση, η δική του μηχανική εκτίμηση του HotPDF θέτει την πρόσθετη εξοικονόμηση σε περίπου 30 έως 60 τοις εκατό μικρότερη από όσο επιτυγχάνει μόνη της η αποδιπλασιοποίηση σε επίπεδο ροής, για μια τυπική πολυσέλιδη σάρωση χτισμένη από μία επαναλαμβανόμενη γραμματοσειρά — το εύρος κινείται ανάλογα με το πόσο από το οπτικό λεξιλόγιο του εγγράφου πράγματι επαναλαμβάνεται, αφού μια σελίδα γεμάτη μοναδικά διαγράμματα δεν δίνει τίποτα στο λεξικό να επαναχρησιμοποιήσει. Αντιμετωπίστε το ως στόχο σχεδιασμού αντί για εγγύηση για οποιαδήποτε συγκεκριμένη είσοδο, και μετρήστε τα δικά σας έγγραφα αντί να εμπιστεύεστε έναν μοναδικό αριθμό. Η επίδειξη JBIG2Benchmark που διατίθεται με το HotPDF υπάρχει ακριβώς για αυτόν τον σκοπό: κωδικοποιεί την ίδια πολυσέλιδη σάρωση με τέσσερις διαφορετικούς τρόπους και εκτυπώνει το προκύπτον μέγεθος αρχείου για κάθε ρύθμιση, ώστε η σύγκριση να τρέχει έναντι του δικού σας μείγματος σαρώσεων αντί για ένα συνθετικό
procedure RunScenario(const Title: string; AccumulateGlobals: Boolean);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.JBIG2Options.Lossless := True;
Pdf.JBIG2Options.UseSymbolDictionary := True;
Pdf.JBIG2Options.UseGlobalSegments := True;
Pdf.JBIG2Options.AccumulateGlobalsAcrossPages := AccumulateGlobals;
Pdf.JBIG2Options.UseExternalEncoder := not AccumulateGlobals;
// ... encode the same three-page scan here, then compare file sizes.
finally
Pdf.Free;
end;
end;
begin
RunScenario('Per-image lossless baseline', False);
RunScenario('Cross-page accumulated globals', True);
end.
Πού συναντά η συσσώρευση μεταξύ σελίδων τα όριά της
Το συσσωρευμένο λεξικό περιορίζεται στα 4096 σύμβολα, το ίδιο ανώτατο όριο που ήδη επιβάλλει ο γηγενής κωδικοποιητής ανά εικόνα σε μία σελίδα. Ξεπεράστε εκείνο το όριο στη μέση του εγγράφου και το HotPDF δεν εγείρει εξαίρεση ούτε ματαιώνει την εκτέλεση: ο συσσωρευτής απορρίπτει τον νέο γλύφο, και η σελίδα που τον εισήγαγε επιστρέφει αυτόματα σε ανεξάρτητη κωδικοποίηση ανά εικόνα, οπότε το έγγραφο εξακολουθεί να βγαίνει σωστό — απλώς σταματάτε να παίρνετε την εξοικονόμηση μεταξύ σελίδων για όποιες σελίδες σπρώχτηκαν πέρα από το ανώτατο όριο. Μια δεύτερη διασφάλιση παρακολουθεί το συνολικό μέγεθος αντί για τον αριθμό συμβόλων: μόλις το συνδυασμένο πλάτος συμβόλων του συσσωρευμένου λεξικού ξεπεράσει τα 131071 pixel, το HotPDF αδειάζει την τρέχουσα παρτίδα στον δίσκο και ξεκινά μια νέα ομάδα globals αυτόματα, αντί να αφήνει μια δομή στη μνήμη να μεγαλώνει απεριόριστα. Κανένα από τα δύο όρια δεν χρειάζεται κώδικα από την πλευρά σας, αφού και τα δύο είναι αυτόματα εναλλακτικά αντί για εξαιρέσεις που πρέπει να συλλάβετε
Η συμμόρφωση PDF/A είναι η μία ρύθμιση που απενεργοποιεί ολόκληρο τον μηχανισμό αντί απλώς να τον περιορίζει. Το HotPDF αντικαθιστά σιωπηλά το CCITT Group 4 με το JBIG2 τη στιγμή που το PDFACompliance είναι μη κενό, σε κάθε σελίδα, ανεξάρτητα από το AccumulateGlobalsAcrossPages ή οτιδήποτε άλλο στο JBIG2Options — μια σκόπιμη επιλογή συμμόρφωσης, όχι σφάλμα, αλλά σημαίνει ότι ένα αρχειακό προφίλ και η κοινή χρήση συμβόλων μεταξύ σελίδων είναι σήμερα αμοιβαία αποκλειόμενα. Όποια ρύθμιση κι αν καταλήξετε, αποκωδικοποιήστε ό,τι γράψατε πριν το εμπιστευτείτε: φορτώστε πίσω το αρχείο με LoadFromFile και τραβήξτε κάθε σελίδα μέσω ExtractLoadedImage, το οποίο επιλύει τα κοινά globals για εσάς με τον ίδιο τρόπο που θα το έκανε οποιοσδήποτε συμμορφούμενος αναγνώστης, και συγκρίνετε το αποτέλεσμα έναντι των αρχικών σας bitmap
var
Loaded: THotPDF;
PageBmp: TBitmap;
PageIdx: Integer;
begin
Loaded := THotPDF.Create(nil);
try
Loaded.LoadFromFile('scanned-contract.pdf');
for PageIdx := 0 to Loaded.PagesCount - 1 do
begin
PageBmp := Loaded.ExtractLoadedImage(PageIdx); // resolves the shared globals for you
try
// Compare PageBmp against the source bitmap for this page.
finally
PageBmp.Free;
end;
end;
finally
Loaded.Free;
end;
end;
Η κοινή χρήση λεξικού μεταξύ σελίδων αγγίζει μόνο την πλευρά εικόνων δύο επιπέδων ενός εγγράφου. Αν η ίδια διοχέτευση εκπέμπει επίσης δημιουργημένες σελίδες κειμένου δίπλα στις σαρώσεις — φύλλα εξωφύλλου, σελίδες ευρετηρίου, ένα επίπεδο κειμένου OCR — οι ροές αντικειμένων και οι ροές xref επιτίθενται στο άλλο μισό του προϋπολογισμού μεγέθους αρχείου συμπιέζοντας τη δομή εγγράφου που προσθέτουν αυτές οι σελίδες. Η κοινή χρήση globals JBIG2 μεταξύ σελίδων διατίθεται ως μέρος του εξαρτήματος HotPDF για Delphi και C++Builder, μαζί με τις επιλογές JBIG2 ανά εικόνα και το υπόλοιπο της διοχέτευσης συμπίεσης