Το HotPDF εκθέτει τρεις πυρήνες downsampling εικόνων μέσω της ιδιότητας ImageDownsampleKernel και ένα ξεχωριστό πέρασμα Floyd-Steinberg μέσω του RenderOutputDither. Ο πρώτος ελέγχει πώς φαίνονται οι φωτογραφίες αφού τις μαζέψετε για να πετύχετε budget μεγέθους, ο δεύτερος ελέγχει πώς φαίνονται αφού μια σελίδα περιοριστεί σε άσπρο-μαύρο. Κανένας δεν είναι ενεργός εξ ορισμού, και οι δύο είναι opt-in για τον ίδιο λόγο: κοστίζουν πραγματικό χρόνο
Η πίεση που οδηγεί κόσμο εδώ είναι γνώριμη. Ένα σκαναρισμένο συμβόλαιο 60 MB πρέπει να βγει μέσα από email gateway που απορρίπτει οτιδήποτε πάνω από 10 MB, ή μια παρτίδα καταστάσεων πρέπει να καταλήξει σε μονόχρωμη συσκευή τύπου fax που αποδίδει κάθε γκρι pixel είτε ως χαρτί είτε ως toner. Και τα δύο προβλήματα είναι προβλήματα resampling, και τα δύο έχουν μια γρήγορη απάντηση που φαίνεται άσχημη και μια αργή που φαίνεται σωστή
Σε τι διαφέρουν στην πραγματικότητα οι τρεις πυρήνες
Η THPDFResampleKernel έχει τρεις τιμές, και κάθονται σε πραγματικά διαφορετικά σημεία της καμπύλης ταχύτητας και ποιότητας. Η rkHalftone αναθέτει στην ιστορική διαδρομή GDI StretchBlt με τη λειτουργία HALFTONE, που παρά το όνομα είναι φιλτράρισμα κλάσης bilinear: γρήγορο, επαρκές για line art και screenshots, και επιρρεπές στις τραχιές ακμές που αναγνωρίζεις αμέσως σε μαζεμένες φωτογραφίες. Η rkBicubic τρέχει διαχωρίσιμο πυρήνα Catmull-Rom, και η rkLanczos3 τρέχει διαχωρίσιμο windowed sinc με υποστήριξη τριών λοβών
Και οι δύο διαχωρίσιμοι πυρήνες τρέχουν ως δύο περάσματα, οριζόντιο μετά κάθετο, με 6 έως 12 taps ανά pixel προορισμού σε καθαρή Pascal. Είναι κατά προσέγγιση μια τάξη μεγέθους πιο αργό από τη διαδρομή GDI, που είναι ακριβώς ο λόγος που η rkHalftone μένει η προεπιλογή. Σε νυχτερινή παρτίδα χιλιάδων σελίδων η διαφορά είναι απόφαση προγραμματισμού, όχι προτίμηση. Σε ένα έγγραφο που ένας χρήστης περιμένει, το Lanczos3 είναι σχεδόν δωρεάν και ορατά καλύτερο
Δύο ιδιότητες υλοποίησης αξίζει να ξέρετε γιατί καθορίζουν τι μπορεί και τι δεν μπορεί να κάνει η έξοδος. Τα περιθώρια σφίγγονται με αναπαραγωγή ακμής αντί για αναδίπλωση ή ξεθώριασμα, και τα βάρη κανονικοποιούνται ανά pixel προορισμού. Μαζί, αυτά τα δύο σημαίνουν ότι το αποτέλεσμα δεν χτυπά ποτέ κάτω από μαύρο ή πάνω από άσπρο, οπότε το κλασικό overshoot φωτοστέφανο του Lanczos γύρω από σκληρή ακμή δεν εμφανίζεται ως κομμένα artifacts στην κωδικοποιημένη εικόνα
var
Pdf: THotPDF;
Info: THPDFLoadedResourceOptimizationInfo;
Changed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-contract.pdf');
Pdf.ImageDownsampleKernel := rkLanczos3; // ορίστε πριν την κλήση
Changed := Pdf.DownsampleLoadedImages(150, 82, 4096, Info);
if Changed > 0 then
begin
Writeln('resampled images: ', Info.DownsampledImageCount);
Writeln('kept calibrated : ', Info.PreservedCalibratedImageCount);
Pdf.SaveToFile('scanned-contract-150dpi.pdf');
end;
finally
Pdf.Free;
end;
end;
Το όρισμα MinimumSavingsBytes, 4096 παραπάνω, είναι ο φρουρός που κρατά την πράξη τίμια. Η επαν-κωδικοποίηση μιας εικόνας που ήταν ήδη αποδοτικά συμπιεσμένη μπορεί να παράγει μεγαλύτερο stream από το πρωτότυπο, και ένας downsampler που αντικαθιστά τυφλά κάθε εικόνα θα μεγαλώσει καμιά φορά το αρχείο που του ζητήθηκε να μαζέψει. Το κατώφλι λέει: υποβάλε την αντικατάσταση μόνο όταν εξοικονομεί τουλάχιστον τόσα bytes. Η PreservedCalibratedImageCount αναφέρει την άλλη συντηρητική απόφαση, εικόνες που αφήνονται ανέγγιχτες επειδή κουβαλούν calibrated color space που το resampling θα υπονόμευε
Γιατί ένας λάθος συντελεστής πολυωνύμου είναι τόσο δύσκολο να εντοπιστεί;
Γιατί ένας χαλασμένος πυρήνας παρεμβολής δεν κρασάρει ούτε σηκώνει exception, απλώς παράγει μια εικόνα που φαίνεται λεπτά λάθος με τρόπο που κανείς δεν μπορεί να αποδώσει. Ο πυρήνας Catmull-Rom είναι τεμαδιαία κυβικός, και ο εξωτερικός του κλάδος σε εμφωλευμένη μορφή Horner είναι ((-0.5t + 2.5)t - 4)t + 2. Γράψτε εκείνον τον μεσαίο συντελεστή -5 αντί για -4 και η συνάρτηση ακόμα υπολογίζεται, ακόμα επιστρέφει αριθμούς σε πιθανό εύρος, και ακόμα παράγει εικόνα
Η ζημιά εμφανίζεται ως W(1) που υπολογίζεται σε -1 ενώ πρέπει να είναι 0. Αρνητικά βάρη συσσωρεύονται, το άθροισμα σφίγγεται στο μηδέν, και το ορατό σύμπτωμα είναι μια διαβάθμιση της οποίας το αριστερό άκρο μαυρίζει και μια ακμή βήματος που χάνει τους ενδιάμεσους τόνους της. Τίποτα στην αποτυχία δεν δείχνει πολυώνυμο. Ο έλεγχος που το πιάνει σε δευτερόλεπτα είναι αριθμητικός και όχι οπτικός: ένας πυρήνας παρεμβολής πρέπει να ικανοποιεί W(0) = 1 και W(±1) = W(±2) = 0, και κάθε πυρήνας που χάνει αυτά τα τρία σημεία έχει σφάλμα συντελεστή, τέλος. Κάντε assert σε αυτές τις τρεις τιμές σε unit test και όλη η κατηγορία τυπογραφικών ελαττωμάτων εξαφανίζεται
Floyd-Steinberg dithering, και πού ανήκει στο pipeline
Το πέρασμα dither είναι διαφορετικό πρόβλημα από το resampling και κατοικεί σε διαφορετικό σημείο του pipeline. Το RenderOutputDither εφαρμόζει διάχυση σφάλματος Floyd-Steinberg μετά τη σύνθεση σελίδας, που είναι η μόνη τοποθέτηση που βγάζει νόημα για μονόχρωμη προεπισκόπηση εκτύπωσης ή εξαγωγή τύπου fax: η πράξη αφορά τη μείωση ενός ολοκληρωμένου raster σε ένα bit ανά pixel, όχι το πώς κλιμακώθηκαν οι επιμέρους εικόνες στην πορεία
Ο αλγόριθμος ίδιος είναι σύντομος. Η φωτεινότητα κατωφλιώνεται στο 50 τοις εκατό, και το σφάλμα κβαντοποίησης διαχέεται σε τέσσερις γείτονες με τα κλασικά βάρη 7/16, 3/16, 5/16 και 1/16, προς τα δεξιά, κάτω-αριστερά, κάτω και κάτω-δεξιά. Το pixel εξόδου είναι 0 ή 255 σε κάθε κανάλι. Όσο σας δίνει η ναϊβική εναλλακτική, ένα σκληρό κατώφλι χωρίς διάχυση, μετατρέπει φωτογραφία σε σιλουέτα και χάνει κάθε ενδιάμεσο τόνο που κουβαλούσε το περιεχόμενο
// Dithering την ώρα της απόδοσης για μονόχρωμη συσκευή προεπισκόπησης
Pdf.RenderOutputDither := True;
// Ή εφαρμόστε το ίδιο πέρασμα σε bitmap που έχετε ήδη. Το bitmap πρέπει
// να είναι pf24bit· η συνάρτηση επιστρέφει False αντί να μαντέψει
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// Άμεση πρόσβαση πυρήνα όταν κάνετε resampling εκτός του pipeline εγγράφου
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
Υπάρχει μια λεπτομέρεια υλοποίησης στη διάχυση σφάλματος που δαγκώνει τον καθένα μία φορά. Το buffer σφάλματος από γραμμή σε γραμμή πρέπει να αθροίζει. Κάθε pixel της επόμενης γραμμής λαμβάνει συνεισφορές από τρία διαφορετικά pixels της τρέχουσας γραμμής, τα taps 3/16, 5/16 και 1/16, και αν ο κώδικας αναθέτει αντί να προσθέτει, κάθε εγγραφή απορρίπτει την προηγούμενη συνεισφορά και μόνο το τελευταίο tap επιβιώνει. Η εικόνα εξακολουθεί να φαίνεται dithered, που είναι όσο την κάνει δύσκολη να προσέξει κανείς, αλλά η υφή είναι λάθος και η τονική αναπαραγωγή παρασύρεται. Το test που το πιάνει είναι ποσοτικό: κάντε dither σε ομοιόμορφο πεδίο μεσο-γκρι και απαιτήστε η κάλυψη του εσωτερικού να πέσει μεταξύ 40 και 60 τοις εκατό
Ποιον συνδυασμό να χρησιμοποιήσει ένα pipeline μείωσης μεγέθους;
Ταίριαξτε τον πυρήνα με ό,τι είναι στην πραγματικότητα οι εικόνες, και μεταχειριστείτε το dithering ως ζήτημα συσκευής και όχι συμπίεσης. Για φωτογραφικές σαρώσεις που πρέπει να επιβιώσουν από budget μεγέθους, rkLanczos3 στα 150 ή 200 DPI κρατά τη λεπτομέρεια που παρατηρεί ο κόσμος ενώ κόβει το πλήθος pixels κατά συντελεστή τέσσερα ή περισσότερο. Για screenshots, διαγράμματα και line art, το rkHalftone είναι πραγματικά καλό και πολύ πιο γρήγορο, γιατί εκείνες οι εικόνες έχουν λίγες τονικές διαβαθμίσεις να διαφυλάξουν. Για μικτή παρτίδα όπου δεν μπορείτε να εξετάσετε κάθε εικόνα, το rkBicubic είναι το λογικό μέσο: καλύτερο από bilinear, περίπου μισό πλήθος taps του Lanczos3
Το downsampling είναι ένας μοχλός ανάμεσα σε αρκετούς, και δεν είναι πάντα ο μεγαλύτερος. Οι bilevel σαρώσεις συνήθως ανταποκρίνονται πολύ καλύτερα στον encoder που καλύπτει η εγγενής συμπίεση JBIG2 bilevel σε Delphi, όπου το κέρδος έρχεται από λεξικά symbols και όχι από πλήθη pixels. Πριν αποφασίσετε, βοηθά να ξέρετε τι είναι πραγματικά στο αρχείο, που είναι ο σκοπός της εξαγωγής εικόνων και των decode filters τους: μια απογραφή αντικειμένων εικόνων και της υπάρχουσας συμπίεσής τους σας λέει αν το resampling έχει κάτι να κερδίσει
Αν χτίζετε την επιφάνεια προεπισκόπησης που δείχνει το αποτέλεσμα, η ίδια διαδρομή απόδοσης που τεκμηριώνεται στο rendering σελίδας PDF σε bitmap είναι εκεί όπου δρα το RenderOutputDither, ώστε η dithered προεπισκόπηση και η dithered έξοδος να βγαίνουν από ένα μονοπάτι κώδικα και όχι από δύο υλοποιήσεις που αποκλίνουν
Η φαρδιά αρχή πίσω από και τις δύο δυνατότητες είναι ότι οι ρυθμίσεις ποιότητας πρέπει να είναι ρητές και αναστρέψιμες. Το HotPDF κρατά την ιστορική συμπεριφορά ως προεπιλογή ώστε μια υπάρχουσα εφαρμογή να αναβαθμίζει χωρίς απροσδόκητη αλλαγή σε έξοδο ή χρονισμό, και βάζει τις ομορφότερες, πιο αργές διαδρομές σε απόσταση ανάθεσης μίας ιδιότητας. Και τα δύο είναι μέρος του HotPDF Delphi PDF component, μαζί με τα μηχανήματα βελτιστοποίησης πόρων και απόδοσης πάνω στα οποία χτίζουν