Η απόδοση (Rendering) μιας σελίδας PDF σε ένα JPEG είναι δύο λειτουργίες (operations) που οι άνθρωποι τείνουν (tend) να εκτελούν (run) μαζί και στη συνέχεια (then) να αποσφαλματώνουν (debug) ξεχωριστά. Πρώτα ραστεροποιείτε (rasterize) τη σελίδα σε ένα pixel bitmap σε μια ανάλυση (resolution) που επιλέγετε. Στη συνέχεια, παραδίδετε (hand) αυτό το bitmap σε έναν κωδικοποιητή (encoder) JPEG και επιλέγετε (pick) μια ποιότητα. Το PDFium Component κατέχει (owns) το πρώτο μισό μέσω του RenderPage· το δεύτερο μισό είναι απλό VCL (plain VCL), το TJPEGImage από το Vcl.Imaging.jpeg. Η ραφή (seam) μεταξύ τους είναι όπου "ζουν" οι ενδιαφέρουσες αποφάσεις, επειδή η ανάλυση που επιλέγετε στην πλευρά της απόδοσης και η ποιότητα που επιλέγετε στην πλευρά της κωδικοποίησης (encode side) ανταλλάσσονται (trade off) η μία έναντι της άλλης (against each other) και έναντι (against) του μεγέθους του αρχείου με τρόπους που είναι εύκολο να γίνουν λάθος
Αυτό που πρέπει να εμπεδώσετε (internalize) πριν από οποιονδήποτε κώδικα (code): μια σελίδα PDF δεν έχει pixel. Περιγράφεται σε στιγμές (points), όπου μία στιγμή είναι 1/72 της ίντσας, και η σελίδα είναι ένα διανυσματικό σχέδιο (vector drawing) μετρημένο σε αυτές τις στιγμές. Όταν ζητάτε από το PDFium να αποδώσει (render), επιλέγετε (choosing) σε πόσα pixel θα προβάλετε (project) αυτό το σχέδιο, και αυτή η επιλογή είναι το DPI. Αν κάνετε λάθος την αριθμητική (arithmetic), είτε αποδίδετε μια θολή (blurry) μικρογραφία όταν θέλατε (wanted) ένα κύριο αρχείο εκτύπωσης (print master), είτε εκχωρείτε (allocate) ένα bitmap 200 megapixel για κάτι που προορίζεται (destined) να είναι μια προεπισκόπηση 120 pixel
Από DPI σε διαστάσεις pixel (pixel dimensions)
Το RenderPage θέλει ακέραια (integer) pixel Width και Height (Πλάτος και Ύψος), όχι ένα DPI. Επομένως η πρώτη δουλειά (first job) είναι η μετατροπή (converting). Μια σελίδα αναφέρει (reports) το μέγεθός της (size) σε στιγμές (points) μέσω του PageWidth (ΠλάτοςΣελίδας) και του PageHeight (ΎψοςΣελίδας) (και τα δύο Double), και η μετατροπή (conversion) είναι η ίδια που χρησιμοποιεί κάθε raterizer: τα pixel ισούνται με στιγμές (points) επί το DPI-στόχο διά 72 (divided by 72). Μια σελίδα US Letter είναι 612 επί 792 στιγμές. Στα 150 DPI αυτό γίνεται (becomes) 1275 επί 1650 pixel· στα 72 DPI παραμένει (stays) 612 επί 792, ένα pixel ανά στιγμή (point), που είναι η περίπτωση που οι άνθρωποι ξεχνούν ότι είναι απλώς η ταυτότητα (identity)
// Pdf.PageNumber must already point at the page you want.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... use Bitmap ...
Bitmap.Free; // the function-form RenderPage hands you ownership
Δύο λεπτομέρειες σε αυτές τις τέσσερις γραμμές αποφασίζουν (decide) αν ο κώδικας είναι σωστός. Η πρώτη είναι ότι η συναρτησιακή μορφή (function form) του RenderPage επιστρέφει ένα TBitmap που εσείς κατέχετε (own). Το PDFium το εκχώρησε (allocated) και αποχώρησε (walked away)· αν δεν το απελευθερώσετε (Free) σε κάθε επανάληψη (iteration), μια παρτίδα (batch) πάνω από μερικές εκατοντάδες (few hundred) σελίδες διαρρέει (leaks) μερικές εκατοντάδες bitmaps και η διεργασία (process) διογκώνεται (bloats) μέχρι κάτι να καταρρεύσει. Η δεύτερη είναι το όρισμα (argument) Color (Χρώμα), το clWhite (Λευκό) εδώ. Οι σελίδες PDF συνήθως (usually) σχεδιάζονται υποθέτοντας (assuming) ένα αδιαφανές (opaque) λευκό υπόστρωμα (substrate), και μια σελίδα με διαφάνεια (transparency) που αποδίδεται σε λάθος χρώμα φόντου (background color) παράγει (produces) λασπωμένες (muddy) άκρες ή αδέσποτα (stray) σκοτεινά (dark) φωτοστέφανα (halos). Το λευκό είναι η σωστή προεπιλογή (right default) για σχεδόν κάθε (almost every) έγγραφο· η παράμετρος υπάρχει (exists) για τη σπάνια (rare) περίπτωση (case) όπου δεν είναι
Τα 0, 0 είναι οι μετατοπίσεις (offsets) Left (Αριστερά) και Top (Επάνω) στη σελίδα, στον κλιμακωμένο χώρο συντεταγμένων (scaled coordinate space), και τις αφήνετε (leave) στο μηδέν εκτός αν περικόπτετε (cropping). Το ro0 είναι η περιστροφή (rotation): αφήστε (leave) το στο μηδέν και το PDFium τιμά (honors) όποια περιστροφή η σελίδα ήδη δηλώνει (declares) στην καταχώρισή (entry) της /Rotate, έτσι μια σελίδα που δημιουργήθηκε (authored) οριζόντια (landscape) βγαίνει (comes out) οριζόντια χωρίς να κάνετε τίποτα (doing anything)
Κωδικοποίηση του bitmap ως JPEG
Μόλις το bitmap υπάρξει (exists), το JPEG είναι το εύκολο κομμάτι, και είναι καθαρό Delphi (pure Delphi). Το TJPEGImage.Assign αντιγράφει το bitmap μέσα (in), το CompressionQuality (ΠοιότηταΣυμπίεσης) ορίζει την ποιότητα σε κλίμακα (scale) 1 έως 100, και το SaveToFile γράφει (writes) το αρχείο. Ο μόνος κανόνας σειράς (ordering rule) είναι ότι η ποιότητα (quality) πρέπει να οριστεί πριν από την αποθήκευση, επειδή ρυθμίζει (governs) την κωδικοποίηση (encode) που πυροδοτεί (triggers) το SaveToFile
uses
Vcl.Graphics, Vcl.Imaging.jpeg, PDFium;
procedure SavePageAsJpeg(Pdf: TPdf; PageNumber, Dpi, Quality: Integer;
const FileName: string);
var
Bitmap: TBitmap;
Jpeg: TJPEGImage;
begin
Pdf.PageNumber := PageNumber;
Bitmap := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Dpi / 72),
Round(Pdf.PageHeight * Dpi / 72),
ro0, [], clWhite);
try
Jpeg := TJPEGImage.Create;
try
Jpeg.Assign(Bitmap);
Jpeg.CompressionQuality := Quality; // 1..100
Jpeg.SaveToFile(FileName);
finally
Jpeg.Free;
end;
finally
Bitmap.Free;
end;
end;
Αυτό το ένθετο (nested) try/finally φαίνεται ιδιότροπο (fussy) για έναν βοηθό (helper) μίας σελίδας, και είναι ακριβώς σωστό για μια παρτίδα (batch). Το εσωτερικό μπλοκ (inner block) απελευθερώνει τον κωδικοποιητή (encoder), το εξωτερικό μπλοκ (outer block) απελευθερώνει το bitmap, και οποιοδήποτε (either one) ενεργοποιηθεί (firing) από μια εξαίρεση εξακολουθεί να απελευθερώνει ό,τι κατέχει (owns). Συμπτύξτε (Collapse) τα σε ένα και μια εξαίρεση (exception) κατά τη διάρκεια (during) της κωδικοποίησης (encoding) μπορεί να αφήσει το bitmap αποκλεισμένο (strand). Σε βάθος χρόνου (Over a long run) αυτή είναι η διαφορά μεταξύ ενός μετατροπέα (converter) που τελειώνει και ενός που πεθαίνει (dies) στη σελίδα 300 με ένα κατεστραμμένο (corrupt) αρχείο και ένα παράθυρο διαλόγου εκτός-μνήμης (out-of-memory)
Επιλογή DPI και ποιότητας μαζί
Τα δύο κουμπιά (knobs) δεν είναι ανεξάρτητα (independent) από τον σκοπό (purpose) της εξόδου, και το συνηθισμένο λάθος (common mistake) είναι να ανεβάζετε και τα δύο (turning both up) από προσοχή (out of caution). Μια μικρογραφία (thumbnail) ιστού που αποδίδεται στα 300 DPI και αποθηκεύεται με ποιότητα 95 είναι αρκετές εκατοντάδες kilobyte (kilobytes) που προσποιούνται (pretending) ότι είναι μια εικόνα 120 pixel· το πρόγραμμα περιήγησης (browser) πετάει (throws away) σχεδόν όλο αυτό (almost all of it) κατά την υποβάθμιση (downscale). Ταιριάξτε (Match) την ανάλυση στα pixel που χρειάζεται πραγματικά η έξοδος, και στη συνέχεια επιλέξτε (pick) μια ποιότητα που επιβιώνει (survives) από τη συμπίεση με απώλειες (lossy compression) του JPEG χωρίς ορατά (visible) τεχνουργήματα (artifacts)
| Έξοδος (Output) | DPI | Ποιότητα JPEG |
|---|---|---|
| Μικρογραφία λίστας (List thumbnail) | 72 | 60-70 |
| Προεπισκόπηση στην οθόνη (On-screen preview) | 96-150 | 80-85 |
| Προβολή υψηλής λεπτομέρειας (High-detail viewing) | 200-300 | 85-95 |
| Κύριο αρχείο εκτύπωσης (Print master) | 300-600 | 90-100 |
Η ποιότητα (quality) JPEG αξίζει μια λέξη προσοχής (word of caution) από μόνη της. Δεν είναι ένα γραμμικό (linear) ρυθμιστικό. Το άλμα από το 70 στο 85 αγοράζει (buys) μια πραγματική (real) οπτική (visual) βελτίωση (improvement) για μέτρια (modest) αύξηση του αρχείου (file growth)· το άλμα από το 95 στο 100 περίπου διπλασιάζει το αρχείο για μια διαφορά που σχεδόν (almost) κανείς δεν μπορεί να δει (nobody can see), επειδή η ποιότητα 100 εξακολουθεί (still) να μην είναι χωρίς απώλειες (lossless), απλώς (just) σταματά (stops) να απορρίπτει πολλά (discarding much). Για σελίδες με πολύ κείμενο (text-heavy), η συμπίεση βάσει-μπλοκ (block-based compression) του JPEG μουτζουρώνει (smears) τις αιχμηρές (sharp) άκρες των γλυμμάτων σε αμυδρό "κουδούνισμα" (faint ringing), γι' αυτό η ποιότητα κάτω από περίπου 80 κάνει το κείμενο να φαίνεται σαρωμένο (scanned-looking) σε αυτό που θα έπρεπε να είναι ευκρινής έξοδος (crisp output). Αν οι σελίδες είναι κυρίως κείμενο και μπορείτε να αλλάξετε μορφές (formats), το PNG αποδίδει (renders) αυτό το κείμενο χωρίς το ringing (κουδούνισμα)· το JPEG κερδίζει τη θέση (place) του σε φωτογραφικό (photographic) και μικτό (mixed) περιεχόμενο (content) όπου η συμπίεσή του είναι πραγματικά μικρότερη (smaller)
Γρηγορότερες (Faster), μικρότερες μικρογραφίες (thumbnails)
Όταν ο στόχος (target) είναι μια μικρογραφία παρά (rather than) μια πιστή (faithful) αναπαραγωγή (reproduction), μπορείτε να πείτε στον αποδοτέα (renderer) να κάνει λιγότερη δουλειά. Η παράμετρος Options (Επιλογές) παίρνει (takes) ένα σύνολο (set) σημαιών TRenderOption, και μερικές από αυτές ανταλλάσσουν (trade) την πιστότητα (fidelity) για την ταχύτητα (speed) με τον ακριβώς (exactly) τρόπο που θέλει μια μικρή προεπισκόπηση. Το reGrayscale ρίχνει (drops) το χρώμα, κάτι που και τα δύο αποδίδει (renders) ταχύτερα και παράγει (produces) ένα μικρότερο bitmap για κωδικοποίηση. Τα reNoSmoothImage και reNoSmoothPath παραλείπουν την εξομάλυνση (anti-aliasing) που είναι αόρατη (invisible) σε κλίμακα μικρογραφίας ούτως ή άλλως (anyway)
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Fit the page inside MaxW x MaxH while preserving aspect ratio.
Scale := Min(MaxW / Pdf.PageWidth, MaxH / Pdf.PageHeight);
Result := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Scale),
Round(Pdf.PageHeight * Scale),
ro0, [reGrayscale, reNoSmoothImage], clWhite);
end;
Η περίπτωση της μικρογραφίας δείχνει (shows) επίσης τον πιο καθαρό (cleaner) τρόπο να σκεφτείτε το μέγεθος (sizing). Αντί να περάσετε (going through) από το DPI, υπολογίστε έναν ενιαίο (single) συντελεστή κλίμακας (scale factor) που προσαρμόζει (fits) τη σελίδα (page) μέσα σε ένα οριακό πλαίσιο (bounding box) και διατηρεί τον λόγο διαστάσεων (aspect ratio), το οποίο είναι αυτό που κάνει το Min (Ελάχιστο) των δύο λόγων (ratios). Μια κατακόρυφη (portrait) σελίδα (page) και μια οριζόντια (landscape) σελίδα καταλήγουν (end up) και οι δύο μέσα στο ίδιο κουτί χωρίς παραμόρφωση (distortion), και δεν χρειάζεται ποτέ (never have) να σκεφτείτε (reason) τι DPI αντιστοιχεί (corresponds) στο "προσαρμογή (fit) σε 200 επί 280". Μία προειδοποίηση (caveat) με το reGrayscale: μετατρέπει το περιεχόμενο (content) εικόνας ράστερ (raster) σε γκρι, αλλά τα διανυσματικά γεμίσματα (vector fills) και το κείμενο διατηρούν (keep) τις τιμές (values) χρώματος (color) στη μηχανή, έτσι μια σελίδα που είναι κυρίως διανυσματική τέχνη (vector art) μπορεί να επιστρέψει (come back) λιγότερο (less) μονόχρωμη από ό,τι υποδηλώνει (suggests) το όνομα της σημαίας (flag's). Για ένα πραγματικό αποτέλεσμα πλήρους κλίμακας του γκρι (full-grayscale result), η μετατροπή (converting) του αποδομένου (rendered) bitmap με το GrayscalePdfBitmap είναι η αξιόπιστη διαδρομή
Μαζική επεξεργασία (Batching) ενός ολόκληρου (whole) εγγράφου
Η συναρμολόγησή του (Putting it together) για ένα πλήρες (full) έγγραφο είναι ένας βρόχος (loop) πάνω στο PageCount, με το PageNumber να μετακινείται μία σελίδα τη φορά. Οι σελίδες είναι με βάση το 1: η σελίδα ένα είναι PageNumber := 1, και ο βρόχος εκτελείται στο PageCount συμπεριλαμβανομένου (inclusive), όχι στο PageCount - 1. Το άλλο πράγμα που πρέπει (must) να σέβεται (respect) η παρτίδα (batch) είναι το συμβόλαιο σιωπηλής φόρτωσης (silent-load contract). Η ρύθμιση (Setting) Active := True δεν εγείρει (raises) ποτέ σε ένα κατεστραμμένο (damaged) αρχείο ή έναν λάθος (wrong) κωδικό πρόσβασης· απλώς (just) αφήνει το Active στο False. Ελέγξτε το (Check it) πριν αποδώσετε (render) μια μεμονωμένη σελίδα, ή η πρώτη RenderPage λειτουργεί έναντι (works against) ενός εγγράφου που δεν άνοιξε ποτέ
procedure ExportAllPages(const PdfPath, OutDir: string; Dpi, Quality: Integer);
var
Pdf: TPdf;
I, Digits: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := PdfPath;
Pdf.Active := True;
if not Pdf.Active then
raise Exception.Create('Could not open ' + PdfPath);
Digits := Length(IntToStr(Pdf.PageCount)); // zero-pad so files sort right
for I := 1 to Pdf.PageCount do
SavePageAsJpeg(Pdf, I, Dpi, Quality,
Format('%s\page_%.*d.jpg', [OutDir, Digits, I]));
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Η προσθήκη μηδενικών (zero-padding) μέσω του Digits είναι ένα μικρό πράγμα που σώζει ένα απόγευμα (afternoon) αργότερα (later). Ονομάστε τα αρχεία page_1.jpg έως page_10.jpg και οποιοδήποτε εργαλείο (tool) τα ταξινομεί (sorts) ως συμβολοσειρές (strings) βάζει το page_10 ακριβώς (right) μετά το page_1, ανακατεύοντας (scrambling) τη σειρά (order). Η συμπλήρωση (Padding) στο πλάτος (width) του υψηλότερου αριθμού σελίδας (page number), έτσι ώστε ένα έγγραφο 300 σελίδων να αποδίδει (yields) page_001.jpg, διατηρεί (keeps) τη λεξικογραφική σειρά (lexical order) και τη σειρά σελίδων ταυτόσημη (identical) παντού (everywhere) παρακάτω (downstream)
Για έγγραφα αρκετά μεγάλα ώστε η μετατροπή να απαιτεί αξιοσημείωτο χρόνο (noticeable time), εκτελέστε (run) την εκτός (off) του νήματος (thread) UI ή αντλήστε (pump) μηνύματα μεταξύ των σελίδων, ώστε η εφαρμογή να παραμένει ανταποκρινόμενη (responsive), και δώστε στον χρήστη έναν τρόπο να σταματήσει. Αν αποδίδετε πολύ μεγάλες σελίδες και θέλετε η ακύρωση (cancellation) να "δαγκώνει" (bites) στη μέση της σελίδας αντί (rather than) μόνο μεταξύ των σελίδων, το PDFium Component διαθέτει μια προοδευτική (progressive) διαδρομή (path) απόδοσης (render) με ένα διακριτικό (token) ακύρωσης· αυτός είναι ένας βαρύτερος μηχανισμός από αυτόν που χρειάζονται οι περισσότερες μαζικές εξαγωγές (batch exports), αλλά υπάρχει εκεί όταν μια μεμονωμένη σελίδα στα 600 DPI είναι από μόνη της αρκετά αργή (slow) για να μπλοκάρει
Ένα τελευταίο (last) ζεύγος (pairing) που αξίζει (worth) να γνωρίζετε. Η ραστεροποίηση (Rasterizing) μιας σελίδας απορρίπτει (discards) το επίπεδο κειμένου της (text layer): το JPEG είναι pixel, και οι λέξεις (words) σε αυτό δεν είναι πλέον επιλέξιμες ή αναζητήσιμες. Όταν χρειάζεστε (need) τόσο (both) μια εικόνα (image) όσο (and) και το υποκείμενο κείμενο (underlying text), αποδώστε (render) για την εικόνα και τραβήξτε (pull) το κείμενο (text) ξεχωριστά (separately), το οποίο καλύπτει το συνοδευτικό (companion) κομμάτι για την εξαγωγή (extracting) κειμένου από έγγραφα PDF με το PDFium Component. Οι υπερφορτώσεις (overloads) RenderPage και οι επιλογές (options) απόδοσης που φαίνονται (shown) εδώ είναι μέρος (part) του PDFium Component για Delphi και C++Builder