Article technique

Groupes de fusion et masques de luminosité dans le rendu PDF

HotPDF rend un groupe de transparence PDF peint avec un mode de fusion non Normal en laissant le groupe calculer sa propre fusion : Cb(1 − α) + α·B(Cb, Cs), en utilisant l'alpha du groupe qu'il suit ou récupère par une passe sur noir et une passe sur blanc. Depuis la v2.770.0, la propriété THotPDF.RenderColorimetricLuminosityMasks choisit aussi si les masques de luminosité soft lisent la couleur en RGB pondéré, comme les visionneuses courantes, ou en CIE Y, comme le décrit ISO 32000-1

Les deux changements sortent du même genre de fichier : une page exportée d'un outil de design, où une photo produit repose sur une ombre portée douce. L'ombre est un Form XObject dessiné comme groupe de transparence avec /BM /Multiply, et son fondu est un soft mask de luminosité. Dans les autres visionneuses, l'ombre est un gris clair et neutre. Dans les anciennes builds de HotPDF, elle sortait presque noire et teintée vers la couleur du fond. Deux bugs distincts étaient empilés l'un sur l'autre, et ils méritent d'être démontés parce que tout moteur de rendu qui grandit depuis une conception "objets opaques d'abord" tombera sur les deux

Pourquoi une ombre portée en Multiply se rend-elle trop sombre ?

Une ombre Multiply se rend trop sombre quand le moteur fait la fusion sur le résultat composé du groupe plutôt que sur sa couleur et son alpha propres. Les pixels partiellement transparents de ce résultat contiennent déjà le fond, donc les fusionner avec le fond multiplie le fond par lui-même. La fusion Normal avec une opacité est linéaire et survit à cette erreur sans qu'on la remarque ; Multiply, Screen, Darken et tous les autres modes non linéaires non

Le pipeline de rendu HotPDF gérait à l'origine les modes de fusion objet par objet avec un modèle simple et rapide. Avant de peindre un objet, le moteur photographie les pixels affectés ; après peinture, il compare les bitmaps "avant" et "après" et traite la couleur "après" comme une source opaque Cs, calcule B(Cb, Cs) et la compose avec l'opacité courante. Pour un chemin rempli, un glyphe ou une image opaque, c'est exactement juste : la source est vraiment opaque à l'intérieur de sa forme, et la forme est là où les pixels ont changé

Un groupe de transparence casse cette hypothèse. ISO 32000-1 §11.4 définit un groupe comme quelque chose qui se compose d'abord dans son propre résultat, avec sa propre couleur et son propre alpha, et ne fusionne ensuite avec le fond que comme un objet unique. Là où une ombre est opaque à 30 pour cent, le bitmap "après" contient environ 0.3 de la couleur d'ombre plus 0.7 de la page en dessous. Nourrissez-en Multiply comme source opaque et la formule devient fond fois (une valeur qui contient déjà le fond) : l'ombre s'assombrit et attrape la teinte du fond. Rien ne plante, aucun diagnostic ne se déclenche, et la page se contente d'être fausse, ce qui est la pire façon pour un moteur de rater

Comment HotPDF laisse un groupe de transparence faire sa propre fusion

Depuis la v2.769.3, HotPDF sort la fusion d'un groupe peint sous un mode non Normal du chemin générique par objet pour la placer dans le moteur de groupe lui-même. Le groupe calcule Cb(1 − α) + α·B(Cb, Cs) directement depuis sa propre couleur, son propre alpha et la photo de fond prise pour l'opérateur Do, puis marque le résultat comme déjà fusionné, pour que le Do englobant se contente de le composer avec l'opacité courante au lieu de fusionner une seconde fois. Les objets simples gardent le modèle rapide avant-après, et les groupes en fusion Normal ne sont pas touchés parce que le cas linéaire n'a jamais été faux

La partie difficile est l'alpha. Un groupe isolé (/I true) part d'un fond entièrement transparent, donc le moteur suit déjà pour lui un plan alpha séparé et peut lire α directement. Un groupe non isolé se compose sur ce qui est déjà sur la page et n'a pas de plan alpha propre. HotPDF le récupère en dessinant le groupe deux fois, une fois sur noir et une fois sur blanc :

  • Sur noir, chaque pixel finit en α·Cs
  • Sur blanc, le même pixel finit en α·Cs + (1 − α)
  • La soustraction donne α = 1 − (white − black), puis Cs = black / α

Cela marche parce que toute composition Normal que le moteur effectue, y compris le AlphaBlend GDI pour les images à soft masks et bords anticrénelés, est linéaire en le fond. Le truc a une précondition pourtant : le contenu du groupe lui-même doit fusionner normalement. Si les ressources ExtGState propres au groupe posent un /BM non Normal, les deux passes ne diffèrent plus d'un terme linéaire propre, donc HotPDF ne sépare pas le groupe et retombe sur le comportement précédent. La vérification ne couvre que les ressources du groupe, pas les forms imbriqués. Le coût est réel aussi : ces pages se rendent un peu plus lentement parce que le groupe est dessiné deux fois. Les soft masks construits dans le groupe sont conservés jusqu'à la fin des deux passes, pour que la seconde passe touche le cache de masques au lieu de les reconstruire

// Illustration de la récupération d'alpha par canal (valeurs 0..1).
// Black = groupe dessiné sur fond noir, White = sur fond blanc.
procedure RecoverGroupPixel(Black, White: Double;
  out Alpha, Source: Double);
begin
  Alpha := 1.0 - (White - Black);
  if Alpha < 0.0 then Alpha := 0.0;
  if Alpha > 1.0 then Alpha := 1.0;
  if Alpha > 0.0 then
    Source := Black / Alpha   // un-premultiply
  else
    Source := 0.0;            // totalement transparent : la couleur est sans importance
end;

// Fusion Multiply, puis composition du groupe Cb(1 - a) + a * B(Cb, Cs)
function CompositeMultiply(Backdrop, Source, Alpha: Double): Double;
begin
  Result := Backdrop * (1.0 - Alpha) + Alpha * (Backdrop * Source);
end;

Le code ci-dessus est une illustration des maths, pas une API HotPDF ; le moteur fait la même chose par canal et par pixel au sein de THotPDF.RenderLoadedPageToBitmap. En pratique, un alpha récupéré depuis deux passes 8 bits perd en précision à l'approche de zéro, si bien que les bords très pâles portent du bruit de quantification ; voilà pourquoi la division n'a de sens que là où l'alpha est nettement au-dessus de zéro

Le mode de fusion repasse à Normal à l'intérieur du groupe

Le mode de fusion, comme l'alpha constant et le soft mask, s'applique au groupe de transparence dans son ensemble et pas à chaque objet intérieur, si bien que le contenu d'un groupe doit repartir de la fusion Normal (ISO 32000-1 §11.6.6). HotPDF réinitialisait auparavant l'opacité et le soft mask à l'entrée du groupe mais laissait le mode de fusion tranquille. Le résultat dans un groupe isolé était spectaculaire : le contenu se multipliait dans le fond initial vide du groupe, qui est noir, et tout le groupe se peignait noir

Le même reset vaut quand un soft mask est construit. Si un ExtGState pose à la fois /BM /Multiply et /SMask, le groupe de masque doit être dessiné en fusion Normal ; sinon un groupe de masque blanc devient noir et cache exactement ce que le masque était censé révéler. Les deux resets font partie de la v2.769.3, et tous deux sont invisibles sur les documents simples, voilà comment ils ont pu vivre aussi longtemps

Quelle valeur un soft mask de luminosité doit-il prendre pour un gris moyen ?

Sous ISO 32000-1, un soft mask de luminosité dont le groupe utilise un espace de fusion à base CIE devrait prendre le CIE 1931 Y, et le Y d'un gris moyen sRGB de 0.5 ne vaut qu'environ 0.21 parce que Y est la lumière linéaire. Les visionneuses PDF courantes résolvent plutôt la couleur du groupe en RGB et pondèrent les valeurs encodées à peu près 0.30 / 0.59 / 0.11, ce qui donne environ 0.5 pour le même gris. Depuis la v2.770.0, HotPDF prend par défaut la pondération compatible visionneuses et propose THotPDF.RenderColorimetricLuminosityMasks pour la lecture de la spécification

L'écart n'a rien d'académique. Un fondu dans un export d'outil de design est en général un dégradé dans un groupe ICCBased ou calibré, et les tons moyens sont exactement là où le Y linéaire et le gris encodé gamma divergent le plus. Avec le CIE Y, un masque gris moyen ne laisse passer qu'un cinquième de l'ombre au lieu de la moitié, si bien que les ombres portées et vignettages sortent nettement plus claires que dans toutes les autres visionneuses où le client ouvrira le fichier. Avoir raison au pied de la lettre de la norme et tort aux yeux de tout le monde est un mauvais compromis pour un défaut ; donc le défaut suit les visionneuses et le mode strict tient en une propriété

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('catalog-page.pdf') > 0 then
    begin
      // Défaut (False) : les masques de luminosité pondèrent la couleur résolue
      // comme DeviceRGB, pour correspondre à ce que montrent les visionneuses courantes.
      // True : prendre le CIE Y quand le groupe de masque a un espace à base CIE.
      Pdf.RenderColorimetricLuminosityMasks := False;
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
      if Bmp <> nil then
      try
        Bmp.SaveToFile('catalog-page-viewer.bmp');
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

La propriété ne compte que quand l'espace de fusion du groupe de masque est à base CIE : CalGray, CalRGB, Lab ou ICCBased. Les masques dont le groupe utilise DeviceGray, DeviceRGB ou DeviceCMYK se rendent exactement comme avant sous l'un ou l'autre réglage, puisque la conversion de l'espace périphérique vers le gris est la même dans les deux modes

Pièges : /BC, /CS et le cache de rendu

Un soft mask de luminosité n'est cohérent que son fond : la couleur /BC qui remplit l'aire du masque hors du groupe doit être convertie avec la même règle que la peinture intérieure du groupe. HotPDF a changé les deux ensemble dans la v2.770.0. Convertir la peinture en RGB pondéré mais le fond en CIE Y produit une couture visible là où la bounding box du groupe finit, un bug facile à introduire quand le fond passe par un utilitaire différent du contenu

Les attributs du groupe doivent aussi être valides avant tout cela. HotPDF lit l'espace de fusion depuis l'entrée /CS du groupe et exige que /BC porte exactement un nombre par composante de cet espace : trois pour un espace à base RGB, quatre pour du CMYK, un pour du gris. Un /CS manquant ou un /BC de mauvaise arité fait échouer le masque avant l'exécution du groupe, et la peinture est alors dessinée sans masque plutôt que masquée avec un fond deviné. Separation, DeviceN et NChannel ne sont pas des espaces de fusion légaux, donc la peinture en tons directs dans un groupe de masque passe par sa transformée de teinte vers l'espace alternatif avant que la luminosité ne soit calculée

Enfin, le réglage change les pixels, donc il fait partie de la clé de cache. Les bitmaps rendus avec RenderColorimetricLuminosityMasks on ou off sont mis en cache séparément dans les caches de rendu mémoire et disque, et basculer la propriété entre deux appels à THotPDF.RenderLoadedPageToBitmapCached ne renvoie jamais un bitmap de l'autre mode. Une note opérationnelle : la v2.769.3 a introduit une régression Win64 où les pages avec un groupe de transparence ne posant pas de soft mask propre échouaient au rendu ; c'est corrigé dans la v2.770.1, donc les applications 64 bits doivent utiliser cette version ou plus

// Épreuve côte à côte : rendez la même page dans les deux modes.
// Chaque mode est mis en cache sous sa propre clé, donc les appels répétés sont bon marché.
Pdf.RenderColorimetricLuminosityMasks := False;
ViewerBmp := Pdf.RenderLoadedPageToBitmapCached(PageIndex, 96);

Pdf.RenderColorimetricLuminosityMasks := True;
StrictBmp := Pdf.RenderLoadedPageToBitmapCached(PageIndex, 96);

// ... montrez les deux, puis libérez-les : les appels cachés renvoient des copies propriété de l'appelant
ViewerBmp.Free;
StrictBmp.Free;

Pour l'API de rendu plus large, y compris le choix de DPI, le cache de pages et ce que le moteur approxime encore, voir le rendu d'une page PDF en TBitmap Delphi avec HotPDF. Si votre problème est dans l'autre sens, retirer la transparence d'un fichier pour l'impression ou l'archivage plutôt que la dessiner, les soft masks et l'aplatissement de transparence dans PDFlibPas couvre ce côté

FAQ

HotPDF rend-il correctement les modes de fusion PDF dans les groupes de transparence ?

Oui, depuis la v2.769.3. Un groupe peint en Multiply ou dans un autre mode non Normal fusionne sa propre couleur et son propre alpha avec le fond, et son contenu repart de la fusion Normal comme l'exige ISO 32000-1 §11.6.6

Pourquoi HotPDF rend-il deux fois les groupes non isolés ?

Un groupe non isolé n'a pas de plan alpha propre. HotPDF le dessine sur noir et sur blanc et récupère l'alpha comme 1 − (white − black), ce qui coûte un peu de vitesse mais rend les ombres Multiply correctes

Faut-il mettre RenderColorimetricLuminosityMasks à True ?

Laissez-le à False pour correspondre à ce que montrent les visionneuses PDF courantes. Mettez-le à True seulement quand vous avez besoin de la lecture CIE Y d'ISO 32000-1 pour des groupes de masques à base CIE, par exemple dans une comparaison de conformité

Les groupes de transparence en mode de fusion, les soft masks de luminosité et le moteur de pages qui les dessine font partie du HotPDF Delphi Component, une bibliothèque VCL native pour Delphi et C++Builder qui crée, édite et rend des documents PDF sans DLL externe