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

Απόδοση Χρωμάτων Separation και DeviceN Spot στο Delphi

Το HotPDF αποδίδει χρώματα Separation και DeviceN spot σε φορτωμένες σελίδες PDF επιλύοντας το χώρο χρωμάτων μέσω της HPDFResolveColorSpace, αξιολογώντας τη συνάρτηση μετασχηματισμού τόνου (tint-transform) με τη HPDFEvalTintTransform, και μετατρέποντας το αποτέλεσμα μέσω του εναλλακτικού χώρου σε RGB για την οθόνη. Από την έκδοση v2.375.0, αυτός ο αγωγός (pipeline) αξιολογεί και τους τέσσερις τύπους συναρτήσεων PDF, συμπεριλαμβανομένων των υπολογιστών Type 4 PostScript, οπότε ένα έτοιμο για εκτύπωση αρχείο με μελάνια Pantone εμφανίζει τα πραγματικά του χρώματα αντί για ένα διακριτικό θέσης. Αυτό το άρθρο περιγράφει πώς λειτουργεί ο αγωγός και, εξίσου χρήσιμα, πώς απέτυχε κατά τη διάρκεια της κατασκευής του

Η περίπτωση που ενεργοποιεί το πρόβλημα είναι πάντα η ίδια. Ένας πελάτης λαμβάνει ένα PDF από ένα τυπογραφείο: ο τίτλος είναι ρυθμισμένος σε ένα ονομαστικό spot μελάνι, το εικαστικό της συσκευασίας χρησιμοποιεί μια ανάμειξη DeviceN δύο μελανιών, και το κείμενο του σώματος είναι απλό μαύρο. Στο Acrobat φαίνεται τέλειο. Στην Delphi εφαρμογή σας, ο τίτλος αποδίδεται μαύρος ή, ακόμη χειρότερα, δεν αποδίδεται καθόλου, και ο πελάτης υποβάλλει μια αναφορά σφάλματος (bug report) για το λογισμικό σας και όχι για το αρχείο. Το αρχείο είναι μια χαρά. Ο renderer απλώς δεν υποστηρίζει τους χώρους χρωμάτων που χρησιμοποιεί το αρχείο

Γιατί τα spot χρώματα αποδίδονται ως μαύρα σε ένα πρόγραμμα προβολής PDF;

Τα spot χρώματα αποδίδονται ως μαύρα, ή εξαφανίζονται, όταν ο renderer υλοποιεί μόνο τους τελεστές χρωμάτων της συσκευής (rg, g, k) και αγνοεί τους γενικούς. Το πρότυπο ISO 32000-1 §8.6 ορίζει τρεις ομάδες χώρων χρωμάτων: χώρους συσκευής (DeviceGray, DeviceRGB, DeviceCMYK), χώρους βασισμένους σε CIE (CalGray, CalRGB, Lab, ICCBased) και ειδικούς χώρους (Indexed, Separation, DeviceN, Pattern). Οτιδήποτε εκτός της ομάδας συσκευής επιλέγεται με τους γενικούς τελεστές: οι cs και CS επιλέγουν έναν χώρο με βάση το όνομα από το λεξικό πόρων της σελίδας, και στη συνέχεια οι sc, SC, scn και SCN παρέχουν τις τιμές των συστατικών (components). Ένας renderer που παραλείπει αυτούς τους τελεστές διατηρεί οποιοδήποτε χρώμα ορίστηκε τελευταίο, το οποίο για μια σελίδα που ξεκινά με τον τίτλο της σε spot χρώμα είναι το αρχικό μαύρο του DeviceGray

Το HotPDF πρόσθεσε το γενικό σύνολο τελεστών στον page renderer του στην έκδοση v2.333.0, μαζί με μια ενιαία διαδρομή επίλυσης: κάθε καταχώριση πόρου /ColorSpace, είτε πρόκειται για απλό όνομα, είτε για ενσωματωμένο πίνακα, είτε για έμμεση αναφορά, αναλύεται σε μία εγγραφή THPDFColorSpace, και κάθε αίτημα χρώματος γεμίσματος ή γραμμής διοχετεύεται μέσω μιας μοναδικής κλήσης HPDFResolveColor. Η απαρίθμηση οικογενειών δείχνει την κάλυψη με μια ματιά

type
  THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
                           csfIndexed, csfCalGray, csfCalRGB, csfLab,
                           csfICCBased, csfSeparation, csfDeviceN,
                           csfUnsupported);

function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;

function HPDFResolveColor(const CS: THPDFColorSpace;
  const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;

Μία σχεδιαστική απόφαση αποδίδει επανειλημμένα: η csfUnsupported είναι μια κλάση πρώτης κατηγορίας και όχι σφάλμα. Ένας χώρος που ο renderer δεν μπορεί να ερμηνεύσει υποβαθμίζεται σε μια καθορισμένη εναλλακτική λύση (fallback) αντί να διακοπεί η απόδοση της σελίδας, γεγονός που ταιριάζει με τη συμπεριφορά των δημοφιλών προγραμμάτων προβολής και εμποδίζει ένα μεμονωμένο εξωτικό γέμισμα να αδειάσει ένα κατά τα άλλα αποδόσιμο έγγραφο

Πώς ένας μετασχηματισμός τόνου μετατρέπει μια τιμή μελανιού σε πραγματικό χρώμα;

Ένας χώρος Separation μεταφέρει τρεις πληροφορίες: το όνομα του μελανιού, έναν εναλλακτικό χώρο χρωμάτων και μια συνάρτηση μετασχηματισμού τόνου (tint-transform). Ο πίνακας [/Separation /PANTONE485 /DeviceCMYK f] λέει: όταν η ροή περιεχομένου γράφει 0.8 scn, τροφοδοτήστε την τιμή τόνου 0.8 στη συνάρτηση f και σχεδιάστε την προκύπτουσα τετράδα CMYK. Το DeviceN το γενικεύει αυτό σε N μελάνια με μια συνάρτηση N εισόδων. Το ίδιο το όνομα του μελανιού είναι μόνο συμβουλευτικό στην οθόνη· ο μετασχηματισμός τόνου είναι ολόκληρη η σημασιολογία απόδοσης, οπότε ένας renderer που αναλύει τον χώρο αλλά παραλείπει τη συνάρτηση δεν έχει κάνει ακόμα τίποτα χρήσιμο

Το HPDFEvalTintTransform είναι η μηχανή συναρτήσεων πίσω από αυτό το βήμα. Ενσωματώθηκε στην έκδοση v2.334.0 και αξιολογεί εκθετικές συναρτήσεις Type 2 (C0 + x^N * (C1 - C0) με περιορισμό Domain και Range), συναρτήσεις συρραφής Type 3 (αναδρομή υποσυναρτήσεων επιλεγμένων με βάση τα Bounds με επαναχαρτογράφηση Encode) και δειγματοληπτικές συναρτήσεις Type 0 με δείγματα 8-, 16- και 32-bit. Η Type 0 είναι η ίδια δομή πίνακα αναζήτησης (lookup-table) που καλύψαμε από την πλευρά της δημιουργίας στο άρθρο για την κατασκευή πινάκων lookup χρωμάτων Type 0· η πλευρά της απόδοσης διατρέχει την ίδια ακριβώς δομή αντίστροφα, από τα αποκωδικοποιημένα byte δείγματος πίσω στις τιμές των συστατικών

Οι συναρτήσεις αριθμομηχανής PostScript Type 4 ήταν το τελευταίο προπύργιο. Μέχρι την έκδοση v2.375.0 υποβαθμίζονταν ομαλά σε ένα ουδέτερο διακριτικό θέσης· από την έκδοση v2.375.0 η HPDFEvalPostScriptCalculator εκτελεί το πλήρες σύνολο τελεστών ISO 32000-1 §7.10.5 σε μια περιορισμένη στοίβα τελεστέων: αριθμητική, σύγκριση, λογικοί τελεστές και τελεστές bitwise, χειρισμός στοίβας συμπεριλαμβανομένου του roll, και συνθήκες if/ifelse. Οι ειδικές σημασιολογίες (corner semantics) είναι πιο αυστηρές από ό,τι φαίνεται. Το PostScript round στρογγυλοποιεί το μισό προς τη μεγαλύτερη τιμή, επομένως η τραπεζική στρογγυλοποίηση Round της Delphi δεν μπορεί να χρησιμοποιηθεί· οι τριγωνομετρικοί τελεστές λειτουργούν σε μοίρες, με την atan να επιστρέφει τιμές στο [0, 360)· και το exp είναι μια δύναμη δύο τελεστέων, όχι ο φυσικός εκθέτης. Ο ίδιος αξιολογητής καθοδηγεί επίσης τα γεμίσματα διαβάθμισης (gradient fills) που βασίζονται σε συναρτήσεις, γι' αυτό και η απόδοση αξονικής και ακτινικής σκίασης απέκτησε ράμπες που καθοδηγούνται από την αριθμομηχανή στην ίδια έκδοση

// Evaluate a Separation/DeviceN tint transform (Type 0/2/3/4).
function HPDFEvalTintTransform(FuncObj: THPDFObject;
  const Inputs: THPDFColorComps; InputCount: Integer;
  out AltComps: THPDFColorComps): Boolean;

// Type 4 PostScript calculator, ISO 32000-1 7.10.5 operator set.
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
  const Inputs: THPDFColorComps; InputCount: Integer;
  var Outputs: THPDFColorComps; OutCount: Integer): Boolean;

Ειλικρίνεια σχετικά με την ακρίβεια: μια αριθμομηχανή λογισμικού που αξιολογείται σε διπλή ακρίβεια (double precision) δεν θα ταιριάζει με έναν RIP bit προς bit, και ο περιορισμός στο όριο Range μπορεί να διαφέρει κατά το λιγότερο σημαντικό bit (least significant bit) από άλλη υλοποίηση. Για την εμφάνιση στην οθόνη και την απόδοση παλινδρόμησης (regression rendering) αυτό είναι άσχετο· εάν κατασκευάζετε δοκίμια με διαχείριση χρωμάτων, ο μετασχηματισμός τόνου είναι μόνο το πρώτο στάδιο και εξακολουθείτε να χρειάζεστε μια πραγματική μονάδα CMM στη συνέχεια

CalGray, CalRGB, Lab και ICCBased χωρίς μηχανή ICC

Οι οικογένειες που βασίζονται στο CIE ακολουθούν τον άλλο κλάδο του ίδιου επιλυτή (resolver). Το HotPDF μετατρέπει τις τιμές Lab μέσω της τυπικής αλυσίδας Lab σε XYZ σε sRGB, συμπεριλαμβανομένου του κύβου σημείου διακοπής (breakpoint cube) 6/29 στην αντίστροφη συνάρτηση μεταφοράς, και χειρίζεται το CalRGB με το gamma ανά κανάλι συν τον γραμμικό πίνακα 3x3, καθώς και το CalGray με το ενιαίο gamma του. Η λεπτομέρεια που σχετίζεται με την απόδοση είναι ο χειρισμός του λευκού σημείου (white point): ο πίνακας μετατροπής XYZ σε sRGB είναι προσαρμοσμένος κατά Bradford (Bradford-adapted) στο λευκό σημείο που δηλώνεται στο χώρο χρωμάτων και αποθηκεύεται προσωρινά (cached) στην επιλυμένη εγγραφή THPDFColorSpace, οπότε η εργασία ανά pixel παραμένει ένας απλός πολλαπλασιασμός 3x3, ανεξάρτητα από το πόσο εξωτικό είναι το δηλωμένο φωτιστικό σώμα (illuminant)

Οι χώροι ICCBased λαμβάνουν μια σκόπιμα πραγματιστική μεταχείριση. Η προδιαγραφή PDF απαιτεί από κάθε ροή ICCBased να δηλώνει έναν χώρο /Alternate ή έναν υπονοούμενο χώρο μέσω του αριθμού των συστατικών του /N, ακριβώς για να μπορούν οι θεατές χωρίς μηχανή διαχείρισης χρωμάτων να κάνουν απόδοση με λογικό τρόπο. Το HotPDF επιλύει το ICCBased μέσω αυτού του εναλλακτικού χώρου, ή μαντεύει τα DeviceGray, DeviceRGB ή DeviceCMYK από το αν το /N είναι 1, 3 ή 4 όταν η καταχώριση απουσιάζει, και δεν αναλύει ποτέ τα byte του προφίλ. Αυτό σημαίνει έλλειψη εξάρτησης από lcms και μηδενικό κόστος αναζήτησης προφίλ, με κόστος όμως τη χρωματομετρική ακρίβεια: ένας χώρος ICCBased του οποίου το προφίλ αποκλίνει έντονα από τον εναλλακτικό του θα εμφανίσει την εναλλακτική απόδοση. Για την προβολή στην οθόνη και τις μικρογραφίες (thumbnails), αυτός είναι ο ίδιος συμβιβασμός που κάνει κάθε ελαφρύ πρόγραμμα προβολής, και είναι το όριο που πρέπει να δηλώσετε ξεκάθαρα στη δική σας τεκμηρίωση

Το σφάλμα που έκανε κάθε αναζήτηση ονομαστικού χώρου χρωμάτων να αποτυγχάνει

Οι σωληνώσεις του τελεστή της έκδοσης v2.333.0 στάλθηκαν με ένα ελάττωμα που παρέμεινε αόρατο για σαράντα δύο εκδόσεις: οι χειριστές cs και CS αναζητούσαν τον τελεστέο τους, με την αρχική κάθετο άθικτη (/CS0), έναντι κλειδιών λεξικού πόρων αποθηκευμένων χωρίς την κάθετο (CS0). Κάθε αναζήτηση ονομαστικού χώρου χρωμάτων αποτύγχανε, στο εκατό τοις εκατό των περιπτώσεων, και ο κώδικας επέστρεφε σιωπηλά στο προεπιλεγμένο DeviceGray. Το ορατό σύμπτωμα ήταν ανεπαίσθητο με τον χειρότερο τρόπο: ένα γέμισμα Separation του 1 scn γινόταν DeviceGray 1.0, το οποίο σχεδιάζει λευκό, και το λευκό μελάνι σε μια λευκή σελίδα δεν είναι ένα σφάλμα απόδοσης που θα έβγαζε κανείς screenshot. Η διόρθωση, στην έκδοση v2.375.0, ήταν ένας κοινός βοηθός κανονικοποίησης ονομάτων (name-normalization helper) που εφαρμόζεται σε κάθε αναζήτηση τελεστέου-προς-πόρο

Δύο αδελφά ελαττώματα προέκυψαν από την ίδια έρευνα. Πρώτον, οι έμμεσες αναφορές (indirect references) σε αντικείμενα με τιμές πίνακα επιστρέφονταν ανεπίλυτες: η πρόσβαση εγγράφου του renderer είχε πληκτρολογήσει επιλυτές μόνο για ροές και λεξικά, οπότε το /CS0 5 0 R που έδειχνε σε έναν αυτόνομο πίνακα [/Separation ...] επέστρεφε ως η ακατέργαστη αναφορά και ο χώρος αναλυόταν ως μη υποστηριζόμενος. Δεύτερον, η HPDFReadNumericArray επέβαλλε αυστηρή σημασιολογία μήκους, απαιτώντας ο πίνακας PDF να είναι τουλάχιστον εξίσου μακρύς με τον παρεχόμενο buffer. Η ανάγνωση των /C0 και /C1 μιας συνάρτησης Type 2 σε έναν buffer τεσσάρων στοιχείων αποτύγτανε επομένως για εναλλακτικούς χώρους ενός και τριών συστατικών, αφήνοντας και τους δύο πίνακες μηδενισμένους, και κάθε μη-CMYK εκθετικός τόνος αποδιδόταν μαύρος έως ότου η έκδοση v2.376.0 εισήγαγε τον πιο ελαστικό αναγνώστη HPDFReadNumericArrayUpTo. Το μάθημα που μπορεί να μεταφερθεί: οποιοδήποτε κλειδί PDF που τεκμηριώνεται ότι περιέχει έναν αριθμητικό πίνακα μεταβλητού μήκους πρέπει να διαβάζεται με έναν αναγνώστη "γέμισε ό,τι υπάρχει", επειδή ένας buffer σταθερού μεγέθους με αυστηρή αντιστοίχιση μετατρέπει τα έγκυρα αρχεία σε σιωπηλά μηδενικά

Πώς δοκιμάζετε την απόδοση spot χρωμάτων χωρίς να κοροϊδεύετε τον εαυτό σας;

Το άβολο ερώτημα είναι γιατί η σουίτα δοκιμών (test suite) παρέμενε πράσινη (green) μέσα από όλο αυτό. Η αρχική παλινδρόμηση, η SeparationRendersDistinguishable, επιβεβαίωνε μόνο ότι το αποδοθέν bitmap δεν ήταν εντελώς μαύρο. Ένας αγωγός που υποβάθμιζε κάθε spot χρώμα σε DeviceGray παρήγαγε γκρίζο και λευκό αποτέλεσμα, το οποίο δεν είναι μαύρο, οπότε η επιβεβαίωση (assertion) περνούσε ενώ ολόκληρη η δυνατότητα ήταν νεκρή. Αδύναμες επιβεβαιώσεις της μορφής "όχι κενό", "όχι εντελώς μαύρο" ή "το digest δεν είναι μηδενικό" δεν μπορούν να ξεχωρίσουν έναν λειτουργικό renderer από έναν χαλασμένο, επειδή σχεδόν κάθε κατάσταση αποτυχίας εξακολουθεί να παράγει ορισμένα pixel

Το στυλ επιβεβαίωσης που πραγματικά συλλαμβάνει αυτές τις αποτυχίες κλειδώνει το αναμενόμενο χρώμα: αποδώστε ένα χειροποίητο ελάχιστο PDF του οποίου το μελάνι Separation επιλύεται σε μια γνωστή απόχρωση, και στη συνέχεια μετρήστε τα pixel που κυριαρχούν σε αυτήν την απόχρωση. Η απόδοση σε ένα bitmap για επιθεώρηση χρησιμοποιεί το ίδιο σημείο εισόδου RenderLoadedPageToBitmap που περιγράφεται στον οδηγό απόδοσης σελίδας σε bitmap

var
  Pdf: THotPDF;
  Bmp: TBitmap;
  X, Y, RedHits: Integer;
  Px: TColor;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
      try
        RedHits := 0;
        for Y := 0 to Bmp.Height - 1 do
          for X := 0 to Bmp.Width - 1 do
          begin
            Px := Bmp.Canvas.Pixels[X, Y];
            if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
               (GetBValue(Px) < 100) then
              Inc(RedHits);
          end;
        // Lock the expected ink: demand a real area of red-dominant
        // pixels, never settle for "not all black".
        Assert(RedHits > 500);
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Το test fixture έχει εξίσου μεγάλη σημασία με την επιβεβαίωση. Ένα χειροποίητο PDF μήκους λίγων εκατοντάδων byte, με ένα μόνο γέμισμα Separation και τίποτα άλλο, δεν αφήνει καμία ασάφεια σχετικά με το ποιο είναι το αναμενόμενο αποτέλεσμα· ένα αρχείο του πραγματικού κόσμου εξασκεί περισσότερο κώδικα, αλλά δεν μπορεί να σας πει ποιο στάδιο απέτυχε. Τώρα αντιμετωπίζουμε τη μέτρηση pixel αναμενόμενου χρώματος ως το ελάχιστο όριο για οποιαδήποτε δοκιμή smoke rendering, επειδή είναι το μόνο στυλ επιβεβαίωσης που ανάγκασε αυτά τα ελαττώματα να αποκαλυφθούν

Η απόδοση χρωμάτων Separation και CIE είναι μέρος του αγωγού φορτωμένων εγγράφων στο HotPDF Component για Delphi και C++Builder, μαζί με τη μηχανή συναρτήσεων, την απόδοση σκίασης και τις διαδρομές εξαγωγής bitmap που εμφανίζονται παραπάνω