Τεχνικό Άρθρο

Προσθήκη Εικόνων JPEG 2000 σε PDF στη Delphi με το HotPDF

Μια ιατρική διαφάνεια σάρωσης, ένα πλακίδιο αεροφωτογράφησης, ένα καρέ φιλμ αρχειοθετημένο σε πλήρες δυναμικό εύρος. Αυτές είναι οι εικόνες που φτάνουν ως JPEG 2000, και φτάνουν έτσι για κάποιο λόγο. Ο μορφότυπος διατηρεί 12 ή 16 bit ανά κανάλι, συμπιέζει με μετασχηματισμό wavelet αντί για το block DCT που χρησιμοποιεί το JPEG, και μπορεί να κωδικοποιήσει την ίδια εικόνα είτε χωρίς απώλειες είτε με απώλειες από ένα μόνο codestream. Όταν ένα έγγραφο που δημιουργείται από αυτές τις πηγές πρέπει να γίνει PDF, η εικόνα πρέπει να περάσει μέσα από ένα φίλτρο που η προδιαγραφή του PDF διατηρεί ακριβώς για αυτόν τον κωδικοποιητή

Το HotPDF v2.228.0 επανέφερε μια λειτουργική μηχανή αποκωδικοποίησης JPEG 2000 για αυτήν τη διαδρομή. Μια παλαιότερη έκδοση είχε διαθέσει τη μονάδα με εικονικές συναρτήσεις (stub functions) που επέστρεφαν nil, οπότε το API υπήρχε αλλά δεν αποκωδικοποιούσε τίποτα. Η τρέχουσα μηχανή συνδέει στατικά το OpenJPEG 2.5.4 και μετατρέπει μια πηγή JP2 ή J2K σε pixel που το HotPDF μπορεί να τοποθετήσει σε μια σελίδα

Το φίλτρο JPXDecode στο PDF

Το ISO 32000-1 ορίζει το φίλτρο JPXDecode στην §7.4.9. Ένα PDF image XObject κατονομάζει τη συμπίεσή του στην καταχώριση /Filter του λεξικού ροής (stream dictionary), και το JPXDecode είναι η τιμή που δηλώνει ότι τα δεδομένα ροής είναι ένα codestream JPEG 2000 αντί για το βασικό JPEG που φέρει το /DCTDecode. Το φίλτρο είναι αυτό που επιτρέπει σε ένα PDF να περιέχει δεδομένα εικόνας συμπιεσμένα με wavelet και μεγάλο βάθος bit, και αποδέχεται τόσο τη λειτουργία χωρίς απώλειες όσο και τη λειτουργία με απώλειες του κωδικοποιητή, επειδή η λειτουργία είναι ιδιότητα του ίδιου του codestream και όχι του περιτυλίγματος γύρω από αυτό

Αυτό το τελευταίο σημείο αξίζει να το θυμάστε. Το JPEG 2000 είναι ένας ενιαίος αλγόριθμος με μια ειδική περίπτωση χωρίς απώλειες, όχι δύο ξεχωριστοί μορφότυποι. Το αντιστρέψιμο wavelet 5/3 ανακατασκευάζει ακριβώς τα αρχικά δείγματα· το μη αντιστρέψιμο wavelet 9/7 ανταλλάσσει αυτή την ακρίβεια με ένα μικρότερο αρχείο. Ένας αποκωδικοποιητής αντιμετωπίζει και τα δύο με τον ίδιο τρόπο κατά την ανάγνωση, γι' αυτό και το HotPDF χρειάζεται μόνο μία διαδρομή αποκωδικοποίησης για να δεχτεί οτιδήποτε του προσφέρει μια ροή JPXDecode

Τι κάνει ο αποκωδικοποιητής στα pixel

Τα PDF image XObjects στη συνήθη περίπτωση αναμένουν 8 bit ανά κανάλι σε DeviceGray ή DeviceRGB. Το JPEG 2000 ξεπερνά συχνά αυτή την τιμή, και το μοντέλο των καναλιών του είναι πιο γενικό από ένα συμπιεσμένο raster, οπότε ο αποκωδικοποιητής έχει τρεις δουλειές να κάνει προτού τα δεδομένα γίνουν αξιοποιήσιμα ως μια κανονική εικόνα

Πρώτον, τα κανάλια με μεγάλο βάθος bit επαναδειγματοληπτούνται (resampled) σε 8 bit. Ένα δείγμα 12-bit ή 16-bit κλιμακώνεται (scaled down) στο εύρος 0 έως 255 ώστε το αποτέλεσμα να είναι ένα συνηθισμένο raster 8-bit. Τα προσημασμένα κανάλια (signed components) μετατοπίζονται πρώτα σε απρόσημο εύρος. Αυτή η λεπτομέρεια έχει σημασία επειδή είναι από μόνη της μια διαδικασία με απώλειες: μια σάρωση κλίμακας του γκρι 16-bit χάνει το βαθύ τονικό της εύρος τη στιγμή που γίνεται εικόνα PDF 8-bit, γεγονός που αποτελεί τον σωστό συμβιβασμό για έξοδο στην οθόνη και εκτύπωση, αλλά όχι για επαναρχειοθέτηση

Δεύτερον, ένας χρωματικός χώρος YCbCr (ο κωδικοποιητής τον ονομάζει SYCC) μετατρέπεται σε RGB. Το JPEG 2000 αποθηκεύει συχνά το χρώμα σε έναν χώρο φωτεινότητας-χρωμικότητας (luma-chroma) για αποδοτικότητα συμπίεσης, την ίδια ιδέα που χρησιμοποιεί το βασικό JPEG, και ο αποκωδικοποιητής εφαρμόζει τον τυπικό αντίστροφο μετασχηματισμό ώστε η σελίδα να λάβει αληθινό RGB

Τρίτον, τα υποδειγματοληπτημένα κανάλια υφίστανται υπερδειγματοληψία (upsampling) μέσω αναπαραγωγής του πλησιέστερου γείτονα (nearest-neighbor replication). Τα κανάλια χρωμικότητας αποθηκεύονται συχνά στη μισή ανάλυση, επομένως ο αποκωδικοποιητής διαβάζει κάθε κανάλι στις δικές του διαστάσεις και με τον δικό του παράγοντα δειγματοληψίας, και στη συνέχεια αναπαράγει δείγματα για να φέρει κάθε κανάλι στο πλήρες μέγεθος της εικόνας πριν από την παρεμβολή (interleaving). Ο πλησιέστερος γείτονας διατηρεί αυτό το βήμα φθηνό υπολογιστικά· η χρωμικότητα που γεμίζει ήταν χαμηλής συχνότητας εξ αρχής, οπότε το ορατό κόστος είναι μικρό

Τα κουτιά JP2 σε σύγκριση με ένα ακατέργαστο J2K codestream

Ένα αρχείο JPEG 2000 έρχεται σε δύο μορφές, και το HotPDF εντοπίζει ποια από τις δύο διαβάζει από τα πρώτα byte αντί από την επέκταση του αρχείου. Ένα αρχείο JP2 είναι ένας περιέκτης (container) με δομή κουτιών: ξεκινά με το κουτί υπογραφής (signature box) δώδεκα byte 00 00 00 0C 6A 50 20 20 και τυλίγει το codestream μαζί με κουτιά που περιγράφουν τον χρωματικό χώρο, την ανάλυση και τα μεταδεδομένα. Ένα ακατέργαστο J2K codestream δεν φέρει καθόλου περιέκτη και ξεκινά με τον δείκτη (marker) SOC FF 4F FF 51. Ο αποκωδικοποιητής διαβάζει αυτά τα αρχικά byte, αναγνωρίζει την υπογραφή και επιλέγει τον αντίστοιχο κωδικοποιητή OpenJPEG για κάθε περίπτωση

Και οι δύο μορφές υποστηρίζονται επειδή απαντώνται στην πράξη. Οι συσκευές λήψης και τα αρχεία που χρειάζονται τα παράπλευρα μεταδεδομένα παράγουν JP2· τα εργαλεία που θέλουν το μικρότερο δυνατό φορτίο δεδομένων παράγουν το γυμνό codestream. Ο τύπος του μορφότυπου μοντελοποιείται ως enum, το TJpeg2000FileType, με τα μέλη jtInvalid, jtJP2, jtJ2K και jtJPT. Το μέλος JPT κατονομάζει την παραλλαγή ροής JPIP· ο ανιχνευτής υπογραφής byte επιλύει τις δύο μορφές που μπορεί να αποκωδικοποιήσει, τις JP2 και J2K, και αναφέρει οτιδήποτε άλλο ως jtInvalid, ώστε μια μη υποστηριζόμενη είσοδος να αποτυγχάνει καθαρά αντί να παράγει άχρηστα δεδομένα

uses
  HPDFJpeg2000;

var
  Decoder: THPDFJpeg2000Decoder;
  Pixels: TJpeg2000ByteArray;
begin
  Decoder := THPDFJpeg2000Decoder.Create;
  try
    if Decoder.LoadFromStream(Input) then          // JP2 or J2K, auto-detected
      if Decoder.GetImageData(Pixels) then
        // Pixels is 8-bit interleaved, ColorComponents channels wide,
        // row-major top to bottom: ready for a DeviceGray/DeviceRGB XObject.
        ProcessRaster(Decoder.Width, Decoder.Height,
                      Decoder.ColorComponents, Pixels);
  finally
    Decoder.Free;
  end;
end;

Χωρίς απώλειες και με απώλειες στην πλευρά της κωδικοποίησης

Ο αποκωδικοποιητής διαβάζει και τις δύο λειτουργίες χωρίς να του υποδειχθεί ποια είναι. Η επιλογή γίνεται παράμετρος μόνο όταν προχωράτε αντίστροφα και παράγετε ένα αρχείο JPEG 2000, το οποίο το HotPDF μπορεί επίσης να κάνει μέσω της κλάσης TJpeg2000Bitmap, ενός απογόνου του TBitmap που φορτώνει και αποθηκεύει δεδομένα raster ως JP2. Δύο ιδιότητες διέπουν την έξοδο. Το LosslessCompression είναι ένα boolean που επιλέγει το αντιστρέψιμο wavelet όταν είναι true· το CompressionQuality είναι ένα TJpeg2000QualityRange, ένας ακέραιος από 1 έως 100 όπου το 1 είναι μικρό και άσχημο και το 100 είναι μεγάλο και πιστό. Οι προεπιλογές βρίσκονται σε επώνυμες σταθερές: το Jpeg2000DefaultLosslessCompression είναι False και το Jpeg2000DefaultLossyQuality είναι 80

Η απόφαση εξαρτάται από το περιεχόμενο. Η συμπίεση χωρίς απώλειες ταιριάζει σε ένα κύριο αντίγραφο (master copy), μια ιατρική ή νομική σάρωση, οτιδήποτε μπορεί να επανακωδικοποιηθεί αργότερα και δεν πρέπει να συσσωρεύσει γενεαλογικές απώλειες. Η συμπίεση με απώλειες σε ποιότητα 80 ταιριάζει σε μια εικόνα που προορίζεται για οθόνη ή εκτύπωση, όπου η ομαλή υποβάθμιση του wavelet δίνει ένα αισθητά μικρότερο αρχείο χωρίς κανένα τεχνούργημα (artifact) που θα πρόσεχε ένας αναγνώστης. Υπάρχει μια προειδοποίηση για το CMYK που πρέπει να επισημανθεί: το bitmap εκθέτει το SetCMYK για να επισημάνει τα δεδομένα τεσσάρων καναλιών ως CMYK αντί για RGBA, γεγονός που έχει σημασία για τις ροές εργασίας εκτύπωσης που διατηρούν τους διαχωρισμούς άθικτους

uses
  HPDFJpeg2000;

var
  Bmp: TJpeg2000Bitmap;
begin
  Bmp := TJpeg2000Bitmap.Create;
  try
    Bmp.LoadFromStream(Source);              // decode an existing JP2/J2K
    Bmp.LosslessCompression := True;         // reversible 5/3 wavelet
    // or, for a smaller lossy file:
    // Bmp.LosslessCompression := False;
    // Bmp.CompressionQuality := 80;         // matches the default
    Bmp.SaveToStream(Output);                // always writes a JP2 file
  finally
    Bmp.Free;
  end;
end;

Γιατί δεν υπάρχει γραμμή επεξεργασίας (pipeline) φίλτρου αποκωδικοποίησης κατά τη φόρτωση

Ένα αρχιτεκτονικό γεγονός διαμορφώνει τον τρόπο που χρησιμοποιείτε οτιδήποτε από αυτά, και είναι εύκολο να υποθέσετε το αντίθετο. Το HotPDF δεν διαθέτει γενικό φίλτρο αποκωδικοποίησης εικόνας κατά τη φόρτωση. Όταν ανοίγετε ένα PDF που περιέχει ήδη μια εικόνα JPXDecode, η μηχανή δεν αποκωδικοποιεί αυτήν τη ροή. Διατηρεί τα byte του JPEG 2000 ακριβώς όπως είναι, επομένως μια αντιγραφή σελίδας ή μια συγχώνευση εγγράφων μεταφέρει την εικόνα άθικτη, byte προς byte. Ο αποκωδικοποιητής έχει ένα μόνο σημείο εισόδου, και αυτό βρίσκεται στην πλευρά της δημιουργίας: το AddImage που βασίζεται σε αρχεία, το οποίο δρομολογείται από την επέκταση αρχείου για να χειριστεί πηγές .jp2, .j2k, .jpt και .jpc

Αυτός ο διαχωρισμός είναι ο σωστός σχεδιασμός και όχι ένας περιορισμός. Η αποκωδικοποίηση μιας ενσωματωμένης ροής JPX κατά τη φόρτωση, μόνο για να επανακωδικοποιηθεί κατά την αποθήκευση, θα μετέτρεπε μια αρχειοθετημένη εικόνα χωρίς απώλειες σε μια εικόνα με απώλειες και θα διόγκωνε κάθε συγχώνευση, όλα αυτά για μια εικόνα που απλώς σκοπεύατε να μετακινήσετε από ένα PDF σε ένα άλλο. Η αυτολεξεί διέλευση (verbatim passthrough) της ροής είναι μια λειτουργία χωρίς απώλειες και ταχύτατη. Η αποκωδικοποίηση αναβάλλεται για τη μοναδική στιγμή που απαιτείται πραγματικά: όταν παραδίδετε στη μηχανή ένα αρχείο JPEG 2000 από τον δίσκο και της ζητάτε να μετατρέψει (rasterize) αυτή την εικόνα για τοποθέτηση σε μια νέα σελίδα. Σε αυτό το σημείο το αρχείο πρέπει να γίνει pixel, και ο αποκωδικοποιητής εκτελείται

Καταχώριση υποστήριξης και τοποθέτηση εικόνας

Η καταχώριση εικόνων JPEG 2000 είναι προαιρετική (opt-in) πίσω από τον διακόπτη μεταγλώττισης HPDF_REGISTER_JPEG2000_PICTURE, ο οποίος είναι απενεργοποιημένος από προεπιλογή. Ο λόγος είναι μια πραγματική διένεξη, όχι η προσοχή: η καθολική καταχώριση των μορφότυπων αρχείων jp2, j2k και jpc με το TPicture μπορεί να παρέμβει στην ανίχνευση του μορφότυπου BLOB στην οποία βασίζεται το TppDBImage του ReportBuilder. Ορίστε τον διακόπτη όταν αυτή η ενσωμάτωση δεν παίζει ρόλο, και οι μορφότυποι αρχείων καταχωρούνται ώστε το TPicture να τους αναγνωρίζει· αφήστε τον απροσδιόριστο και η δρομολόγηση επέκτασης AddImage εξακολουθεί να αποκωδικοποιεί τα αρχεία JPEG 2000 απευθείας, επειδή αυτή η διαδρομή δεν περνάει καθόλου από το TPicture

Έχοντας αυτό κατά νου, η τοποθέτηση μιας εικόνας JPEG 2000 είναι ο ίδιος ρυθμός τριών κλήσεων με οποιαδήποτε άλλη εικόνα του HotPDF. Δώστε στο AddImage μια διαδρομή .jp2 και έναν τύπο συμπίεσης για το πώς θα πρέπει να αποθηκευτεί η εικόνα στην έξοδο, και στη συνέχεια τοποθετήστε το ευρετήριο εικόνας που επιστρέφεται στη σελίδα με το ShowImage

var
  Pdf: THotPDF;
  ImgIndex: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;
    Pdf.AddPage;
    // The .jp2 source is decoded through the OpenJPEG backend, then
    // re-embedded with the compression you request here.
    ImgIndex := Pdf.AddImage('Scan_16bit.jp2', icJpeg);
    // x, y, width, height in points; final 0 is the rotation angle.
    Pdf.ShowImage(ImgIndex, 72, 72, 400, 300, 0);
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Η συμπίεση που περνάτε στο AddImage ελέγχει πώς επαναποθηκεύεται η αποκωδικοποιημένη εικόνα, όχι πώς διαβάστηκε. Ένα αρχείο JPEG 2000 αποκωδικοποιημένο σε bitmap μπορεί να εξαχθεί ξανά ως JPEG DCTDecode, ως raster Flate, ή με κάποιο άλλο υποστηριζόμενο φίλτρο, όποιο ταιριάζει στο έγγραφο. Η αποκωδικοποίηση από JP2 ή J2K συμβαίνει πρώτα ανεξάρτητα από αυτό, οπότε η ίδια κλήση δέχεται μια πηγή συμπιεσμένη με wavelet και την ενσωματώνει σε όποια μορφή περιμένει η υπόλοιπη γραμμή επεξεργασίας σας

Για τη γενικότερη εικόνα του πώς οι εικόνες και οι γραμματοσειρές καταλήγουν στη δημιουργούμενη έξοδο, δείτε τις σημειώσεις μας για την έξοδο αναφορών με γραμματοσειρές και εικόνες. Όταν το έγγραφο που συναρμολογείτε επαναχρησιμοποιεί περιεχόμενο από υπάρχοντα PDF, η συμπεριφορά διέλευσης (passthrough) που περιγράφεται εδώ συνδυάζεται με τους μηχανισμούς συγχώνευσης και αναθεώρησης στις ροές αντικειμένων και τις αυξητικές ενημερώσεις. Η μηχανή αποκωδικοποίησης JPEG 2000 αποστέλλεται ως μέρος του HotPDF Component για Delphi και C++Builder, δίπλα στα API εικόνας, γραμματοσειράς και εγγράφου που καλύπτονται αλλού σε αυτό το ιστολόγιο