Technical Article

LUT de couleurs 3D dans PDF avec des fonctions échantillonnées de type 0

Les fonctions PDF font partie des aspects les plus discrets de la spécification. La plupart des développeurs les découvrent une fois, comme l'élément nécessaire à un ombrage axial de type 2 pour fondre deux couleurs, et ne s'y intéressent plus. C'est regrettable, car le mécanisme des fonctions est un petit évaluateur polyvalent que le format réutilise pour les ombrages, les fonctions de transfert, les fonctions de point de demi-teinte, les teintes de séparation et les courbes de transfert de masque doux. Des quatre types de fonctions, le type 0 est le plus puissant et le moins compris. Il s'agit d'une fonction échantillonnée : une grille multidimensionnelle de valeurs de sortie entre lesquelles le lecteur effectue une interpolation. Comme la grille peut contenir tous les nombres que vous y placez, une fonction de type 0 peut exprimer une cartographie non linéaire arbitraire, ce qui correspond exactement à la forme d'une table de correspondance de couleurs (LUT).

Cet article parcourt le dictionnaire de type 0 tel que défini par la norme ISO 32000-1 au §7.10.2, puis présente les deux cas les plus importants dans un flux de documents : une LUT de correction des couleurs RGB vers RGB à trois entrées, et une transformation de teinte de couleur d'accompagnement (ton direct) à une entrée. Le même générateur de fonction échantillonnée sert dans les deux cas, la différence résidant uniquement dans le nombre d'entrées de la grille.

Une fonction échantillonnée est une grille interpolée par le lecteur

Une fonction de type 0 mappe un vecteur à m entrées vers un vecteur à n sorties en stockant des échantillons sur une grille régulière et en les interpolant. La norme ISO 32000-1 §7.10.2 liste les clés décrivant cette grille. /Domain contient deux nombres par entrée, les limites inférieure et supérieure de chaque axe d'entrée. /Range contient deux nombres par composant de sortie. /Size est un tableau de m entiers indiquant le nombre d'échantillons le long de chaque axe d'entrée ; ainsi, une grille de douze échantillons de côté en trois dimensions a pour /Size [12 12 12] et stocke 1 728 points de grille. /BitsPerSample définit la précision de chaque valeur stockée ; HotPDF accepte 1, 2, 4, 8, 12, 16, 24 et 32 bits, correspondant aux valeurs autorisées par le Tableau 38.

Le flux d'échantillons est lu dans un ordre fixe. La première dimension d'entrée varie le plus rapidement, puis la seconde, et ainsi de suite, et à chaque point de la grille, les n composants de sortie sont stockés dans l'ordre. Pour une table RGB vers RGB, cela représente trois octets par point de grille sous huit bits, disposés dans l'ordre sortie-rouge, sortie-vert, sortie-bleu, en balayant d'abord l'entrée rouge. Deux clés supplémentaires mappent le monde continu sur la grille d'entiers. /Encode mappe chaque entrée de son intervalle /Domain vers la plage d'index d'échantillon 0 à Size[i] - 1, et /Decode mappe les entiers stockés bruts vers les intervalles de /Range. En conservant les valeurs par défaut, une entrée couvrant [0 1] correspond parfaitement à la grille complète et un octet stocké de 255 se décode au maximum de sa plage de sortie, ce qui est le comportement attendu pour une LUT de couleurs normalisée sur [0,1].

Ordre 1 contre Ordre 3

Entre les points de la grille, le lecteur doit effectuer une interpolation, et /Order choisit comment. /Order 1 correspond à une interpolation multilinéaire : linéaire sur un axe, bilinieaire sur deux, trilinéaire sur trois. Elle est rapide, correspond exactement à ce que fait le matériel de la plupart des visualiseurs et, pour une transformation de couleur fluide, elle est généralement indiscernable d'une méthode plus complexe. /Order 3 demande une interpolation par spline cubique, qui ajuste une courbe plus lisse à travers les échantillons au prix d'un travail accru et d'une zone de support plus large autour de chaque point évalué.

Le compromis réside entre la densité de la grille et la fluidité de la courbe. L'ordre cubique s'impose lorsque la grille est grossière et que la correspondance présente une courbure visible, car une ligne droite entre deux échantillons éloignés peut aplatir une courbe de tonalité d'une manière que l'œil perçoit sur les dégradés. Une fois la grille dense, les segments sont assez courts pour que l'interpolation linéaire suive la courbe de près, l'ordre cubique apportant alors peu. Une règle pratique consiste à ne recourir à /Order 3 que pour de petites grilles ou des transformations abruptes, et à conserver le comportement linéaire par défaut dans les autres cas. Notez que /Order ne s'applique qu'aux fonctions de type 0, et HotPDF rejette toute valeur autre que 1 ou 3.

La LUT 3D : trois entrées, trois sorties

Une correction de couleur RGB vers RGB est le cas d'école d'une grille à trois entrées, la LUT 3D classique utilisée pour l'étalonnage des couleurs et la correspondance des périphériques. Chaque axe du cube est un canal d'entrée, chaque point de la grille stocke le triplet RGB corrigé pour cette coordonnée d'entrée et le lecteur effectue une interpolation trilinéaire des échantillons d'angle autour de toute couleur entrante. Trois entrées sont ici incontournables car le rouge corrigé peut dépendre du vert et du bleu d'entrée, et non pas seulement du rouge d'entrée ; une courbe par canal ne peut pas exprimer la diaphonie (crosstalk) entre les canaux, contrairement à un cube.

HotPDF construit le flux de type 0 via RegisterSampledFunction, qui prend directement les octets de /Domain, /Range, /Size, /BitsPerSample et des échantillons, puis renvoie l'objet fonction. Pour un cube normalisé standard, vous passez des limites [0,1] sur les trois axes d'entrée et les trois sorties, une taille N x N x N, et la table d'échantillons aplatie. Le générateür valide que le nombre d'octets correspond à la grille : pour une profondeur alignée sur les octets, il attend OutputCount x (BitsPerSample div 8) x le produit des tailles, et lève une exception si le tableau n'a pas la bonne longueur, de sorte qu'un pas (stride) mal calculé échoue bruyamment à l'enregistrement plutôt que de produire un rendu corrompu plus tard.

const
  N = 17;  // 17 x 17 x 17 cube, the common ICC LUT resolution
var
  LutFn: THPDFStreamObject;
  Samples: TBytes;
begin
  // Fill Samples with N*N*N grid points, 3 bytes each (R,G,B output),
  // red input varying fastest. Build the corrected triple for each
  // grid coordinate with your ICC-managed conversion, then store it.
  SetLength(Samples, N * N * N * 3);
  BuildCorrectedCube(Samples, N);   // your color-managed fill

  LutFn := Pdf.RegisterSampledFunction(
    [0,1, 0,1, 0,1],   // /Domain: three input axes on [0,1]
    [0,1, 0,1, 0,1],   // /Range:  three output channels on [0,1]
    [N, N, N],         // /Size:   the cube resolution per axis
    8,                 // /BitsPerSample
    Samples,
    1);                // /Order 1 = trilinear
end;

La justesse colorimétrique du cube réside dans la manière dont vous le remplissez, et non dans la fonction PDF. La méthode rigoureuse consiste à calculer chaque point de la grille via une conversion gérée par ICC, le même moteur qui pilote un essai-écran (soft-proof), afin que les chiffres de la grille aient un sens par rapport à un profil source et de destination défini. Enregistrez les profils encadrant la conversion avec RegisterICCProfile, qui enregistre un espace colorimétrique basé sur ICC (1, 3 ou 4 composants) et renvoie un nom de ressource que vous pouvez attacher au contenu alimenté par la LUT. La fonction de type 0 porte la table d'interpolation ; le profil ICC porte la signification des extrémités.

Le cas 1D : une transformation de teinte de ton direct

Les espaces colorimétriques de séparation s'appuient sur le même mécanisme pour une tâche totalement différente. Un espace de séparation, défini dans l'ISO 32000-1 §8.6.6.4, représente un colorant unique (une encre de ton direct comme une Pantone ou un vernis) en associant un nom à une transformation de teinte : une fonction qui mappe la valeur de teinte unidimensionnelle (0 pour aucune encre à 1 pour l'encre pleine) sur un espace colorimétrique alternatif que le périphérique peut réellement restituer, généralement le CMYK. Cette transformation de teinte est fréquemment une fonction de type 0, et la grille possède alors exactement un axe d'entrée.

C'est le contraste net avec la LUT 3D. Une encre de ton direct représente un seul degré de liberté, de sorte que sa transformation de teinte nécessite une seule entrée et la grille est une ligne d'échantillons, chacun contenant la valeur CMYK (ou autre alternative) à ce niveau de teinte. Le cube RGB nécessite trois entrées car son domaine est tridimensionnel et les canaux interagissent. Même type de fonction, mêmes règles d'interpolation, dimensionnalité différente ; la spécification réutilise un évaluateur unique et laisse /Size décider si vous parcourez une ligne, un plan ou un cube. HotPDF enveloppe toute la séparation dans RegisterSeparationLUT, qui construit en interne la transformation de teinte de type 0 à une entrée à partir d'un tableau d'octets plat et renvoie le nom de la ressource d'espace colorimétrique.

var
  SpotCS: AnsiString;
begin
  // Four CMYK output bytes per tint grid point, tint domain [0..1].
  // Here 0% ink -> all zero, 100% ink -> a rich spot build,
  // with two interior steps; the tint transform interpolates between.
  SpotCS := Pdf.RegisterSeparationLUT(
    'PANTONE 286 C',         // colorant name
    'DeviceCMYK',            // alternate color space
    [  0,   0,   0,   0,     // tint 0.00 -> 0,0,0,0
      90,  60,   0,   0,     // tint 0.33
     100,  80,   0,  10,     // tint 0.66
     100,  72,   0,  18]);   // tint 1.00 -> full ink build
  // Use SpotCS with SetFillColorSpace / SetFillColor on a page.
end;

Le nombre d'échantillons doit correspondre à un nombre entier de points de grille : un multiple positif du nombre de composants de l'espace alternatif, et au moins deux points pour qu'il y ait un segment à interpoler. Si vous passez trois octets par point pour une alternative CMYK, l'appel le rejette (la même validation défensive que celle appliquée par le générateur 3D), ce qui est ce que vous souhaitez pour éviter qu'une fonction n'échoue silencieusement au moment de l'impression.

Où l'on retrouve ce même mécanisme

Dès lors que vous considérez le type 0 comme une table d'interpolation générique, deux autres fonctionnalités de contrôle de périphérique cessent de ressembler à des cas particuliers. Une fonction de transfert ajuste les valeurs des composants en cours de transmission vers le périphérique de sortie, et il s'agit simplement d'une fonction par canal ; HotPDF l'enregistre en tant qu'ExtGState via RegisterTransferFunctionState, qui accepte soit une fonction combinée unique, soit un tableau de fonctions par canal. Ces fonctions étant des objets fonctions ordinaires, vous pouvez lui transmettre le THPDFStreamObject même renvoyé par RegisterSampledFunction et piloter une courbe de transfert à partir d'une table échantillonnée plutôt que d'une formule.

var
  ToneFn: THPDFStreamObject;
  GsName: AnsiString;
begin
  // A single-input, single-output sampled tone curve on [0,1].
  ToneFn := Pdf.RegisterSampledFunction(
    [0,1], [0,1], [256], 8, ToneCurveBytes, 1);

  // Apply it to all channels as a combined /TR2 transfer function.
  GsName := Pdf.RegisterTransferFunctionState(ToneFn, []);
  // Select GsName on the page before drawing the affected content.
end;

La génération du noir et le retrait sous-couleur (undercolor removal) appartiennent à la même famille. Lorsqu'un périphérique convertit du RGB en CMYK, il décide de la quantité de composant gris à traduire en encre noire, et la spécification exprime cette décision sous forme de fonction (les entrées /BG2 et /UCR2 d'un dictionnaire d'état graphique, chacune étant une courbe à entrée unique allant du gris calculé à une quantité de noir). Ce sont également des fonctions de type 0 lorsque vous souhaitez une courbe mesurée plutôt qu'analytique, construite de la même manière via RegisterSampledFunction et placée dans l'état graphique. La leçon à retenir est que la fonction PDF n'est jamais le lieu où s'effectue la gestion des couleurs ; c'est la table de correspondance qui porte une décision prise avec un véritable moteur de couleurs, et le type 0 est le seul type de fonction assez flexible pour porter n'importe quelle décision.

Pour une vue plus large de la manière dont les polices, les images et les ressources de couleurs sont émises dans un document final, consultez notre guide de génération de rapports avec polices et images. Lorsque la sortie doit passer avec succès une vérification en amont (preflight) d'archivage ou d'impression, les règles d'espace colorimétrique et d'intention de sortie (output intent) traitées dans le guide de validation PDF/A, PDF/X et PDF/UA régissent quelles fonctions sont autorisées et comment la couleur du périphérique doit être balisée. Tout cela est fourni dans le composant HotPDF pour Delphi et C++Builder, avec les API d'ombrage, ICC et de séparation qui reposent sur le même cœur de type 0.