Articolo tecnico

Gruppi blend mode e mask luminosità nel rendering PDF

HotPDF renderizza un gruppo di trasparenza PDF dipinto con un blend mode diverso da Normal lasciando che il gruppo calcoli il suo blend: Cb(1 − α) + α·B(Cb, Cs), usando l'alpha di gruppo che traccia o recupera da un passaggio su nero e uno su bianco. Dalla v2.770.0 la proprietà THotPDF.RenderColorimetricLuminosityMasks sceglie anche se le soft mask di luminosità leggono il colore come RGB ponderato, come i visualizzatori comuni, o come CIE Y, come descrive ISO 32000-1

Entrambe le modifiche sono nate dallo stesso tipo di file: una pagina esportata da uno strumento di design, dove la foto di un prodotto poggia su una soffusa drop shadow. L'ombra è un Form XObject disegnato come gruppo di trasparenza con /BM /Multiply, e il suo sfumato è una soft mask di luminosità. Negli altri visualizzatori l'ombra è un grigio chiaro e neutro. Nelle build HotPDF più vecchie veniva fuori quasi nera e virata verso il colore dello sfondo. Due bug separati erano impilati uno sopra l'altro, e vale la pena smontarli perché qualunque renderer che cresca da un progetto «prima gli oggetti opachi» incontrerà entrambi

Perché una drop shadow Multiply si renderizza troppo scura?

Un'ombra Multiply si renderizza troppo scura quando il renderer fa il blend del risultato composizionato del gruppo invece che del colore e dell'alpha del gruppo stesso. I pixel parzialmente trasparenti di quel risultato contengono già lo sfondo, quindi fare il blend di essi con lo sfondo moltiplica lo sfondo per se stesso. Il blending Normal con una opacità è lineare e sopravvive a questo errore senza farsi notare; Multiply, Screen, Darken e tutti gli altri modi non lineari no

La pipeline di rendering di HotPDF gestiva originariamente i blend mode per oggetto disegnato con un modello semplice e veloce. Prima che un oggetto sia disegnato, il renderer fa uno snapshot dei pixel coinvolti; dopo il disegno confronta le bitmap «prima» e «dopo» e tratta il colore «dopo» come una sorgente opaca Cs, calcolando B(Cb, Cs) e composizionandola con l'opacità corrente. Per un percorso riempito, un glifo o un'immagine opaca è esattamente giusto: la sorgente è davvero opaca dentro la sua forma, e la forma è dove i pixel sono cambiati

Un gruppo di trasparenza rompe quell'assunzione. ISO 32000-1 §11.4 definisce un gruppo come qualcosa che viene prima composizionato nel proprio risultato, con il proprio colore e la propria alpha, e solo dopo fatto in blend con lo sfondo come un singolo oggetto. Dove un'ombra è opaca al 30 percento, la bitmap «dopo» contiene circa 0.3 del colore dell'ombra più 0.7 della pagina sotto di essa. Dare quello in pasto a Multiply come sorgente opaca e la formula diventa sfondo per (un valore che contiene già lo sfondo): l'ombra si scurisce e prende la tinta dello sfondo. Niente crasha, nessuna diagnostica scatta, e la pagina semplicemente sembra sbagliata, che è il modo peggiore per un renderer di fallire

Come HotPDF lascia che un gruppo di trasparenza faccia il suo blend

Dalla v2.769.3, HotPDF sposta il blend di un gruppo dipinto sotto un blend mode diverso da Normal fuori dal percorso generico per oggetto e nel renderer del gruppo stesso. Il gruppo calcola Cb(1 − α) + α·B(Cb, Cs) direttamente dal proprio colore, dalla propria alpha e dallo snapshot dello sfondo preso per l'operatore Do, poi marca il risultato come già in blend così il Do esterno si limita a composizionarlo con l'opacità corrente invece di fare il blend una seconda volta. Gli oggetti semplici tengono il veloce modello prima-dopo, e i gruppi sotto blending Normal non sono toccati perché il caso lineare non è mai stato sbagliato

La parte difficile è l'alpha. Un gruppo isolato (/I true) parte da uno sfondo completamente trasparente, così il renderer traccia già per esso un piano alpha separato e può leggere α direttamente. Un gruppo non isolato si composiziona su qualunque cosa sia già sulla pagina e non ha un piano alpha proprio. HotPDF lo recupera disegnando il gruppo due volte, una su nero e una su bianco:

  • Su nero, ogni pixel finisce come α·Cs
  • Su bianco, lo stesso pixel finisce come α·Cs + (1 − α)
  • Sottraendo si ottiene α = 1 − (white − black), e poi Cs = black / α

Funziona perché ogni composizione Normal che il renderer esegue, incluso il GDI AlphaBlend per le immagini con soft mask e bordi anti-aliasati, è lineare nello sfondo. Il trucco ha una precondizione però: il contenuto del gruppo stesso deve fare blending normalmente. Se le risorse ExtGState del gruppo stesso impostano un /BM diverso da Normal, i due passaggi non differiscono più per un termine lineare pulito, quindi HotPDF non divide il gruppo e ricade nel comportamento precedente. Il controllo copre solo le risorse del gruppo stesso, non i form annidati. Il costo è anche reale: tali pagine si renderizzano un po' più lente perché il gruppo è disegnato due volte. Le soft mask costruite dentro il gruppo vengono tenute finché entrambi i passaggi non finiscono, così il secondo passaggio becca la cache delle mask invece di ricostruirle

// Illustrazione del recupero dell'alpha per canale (valori 0..1).
// Black = gruppo disegnato su sfondo nero, White = su sfondo bianco.
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;            // completamente trasparente: il colore è irrilevante
end;

// Blend Multiply, poi il composito del gruppo Cb(1 - a) + a * B(Cb, Cs)
function CompositeMultiply(Backdrop, Source, Alpha: Double): Double;
begin
  Result := Backdrop * (1.0 - Alpha) + Alpha * (Backdrop * Source);
end;

Il codice sopra è un'illustrazione della matematica, non una API HotPDF; il renderer fa la stessa cosa per canale e per pixel dentro THotPDF.RenderLoadedPageToBitmap. In pratica, l'alpha recuperata da due passaggi a 8 bit perde precisione mentre si avvicina a zero, quindi i bordi molto tenui portano un po' di rumore di quantizzazione, ed è per questo che la divisione ha senso solo dove l'alpha è chiaramente sopra zero

Il blend mode si azzera a Normal dentro il gruppo

Il blend mode, come l'alpha costante e la soft mask, si applica al gruppo di trasparenza nel suo complesso e non a ogni oggetto dentro di esso, quindi il contenuto del gruppo deve partire da blending Normal (ISO 32000-1 §11.6.6). HotPDF prima azzerava opacità e soft mask all'ingresso nel gruppo ma lasciava in pace il blend mode. Il risultato dentro un gruppo isolato era drammatico: il contenuto si moltiplicava nel fondo iniziale vuoto del gruppo, che è nero, e tutto il gruppo veniva dipinto di nero

Lo stesso azzeramento vale quando si costruisce una soft mask. Se un ExtGState imposta insieme /BM /Multiply e /SMask, il gruppo della mask deve essere disegnato con blending Normal; altrimenti un gruppo mask bianco diventa nero e nasconde esattamente ciò che la mask era supposta rivelare. Entrambi gli azzeramenti fanno parte della v2.769.3, e entrambi sono invisibili sui documenti semplici, ed è così che sono vissuti fino a qui

Che valore deve prendere una soft mask di luminosità per il grigio medio?

Secondo ISO 32000-1 una soft mask di luminosità il cui gruppo usa uno spazio di blending basato su CIE dovrebbe prendere CIE 1931 Y, e Y per un grigio medio sRGB di 0.5 è solo circa 0.21 perché Y è luce lineare. I visualizzatori PDF comuni invece risolvono il colore del gruppo in RGB e ponderano i valori codificati circa 0.30 / 0.59 / 0.11, il che dà circa 0.5 per lo stesso grigio. Dalla v2.770.0 HotPDF ha come predefinita la ponderazione compatibile con i visualizzatori e offre THotPDF.RenderColorimetricLuminosityMasks per la lettura della specifica

Il divario non è accademico. Una sfumatura in un'esportazione da strumento di design di solito è un gradiente in un gruppo ICCBased o calibrato, e i toni medi sono esattamente dove Y lineare e grigio con codifica gamma divergono di più. Con CIE Y, una mask grigio medio lascia passare solo circa un quinto dell'ombra invece della metà, quindi drop shadow e vignette vengono fuori notevolmente più chiare che in qualunque altro visualizzatore in cui il cliente apre il file. Avere ragione per la lettera dello standard e torto per gli occhi di tutti è un cattivo scambio per un predefinito, quindi il predefinito segue i visualizzatori e la modalità severa è a una proprietà di distanza

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('catalog-page.pdf') > 0 then
    begin
      // Default (False): le mask di luminosità ponderano il colore risolto
      // come DeviceRGB, alla stregua di ciò che mostrano i visualizzatori comuni.
      // True: prendere CIE Y quando il gruppo della mask ha uno spazio basato su 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 proprietà conta solo quando lo spazio di blending del gruppo della mask è basato su CIE: CalGray, CalRGB, Lab o ICCBased. Le mask il cui gruppo usa DeviceGray, DeviceRGB o DeviceCMYK si renderizzano esattamente come prima sotto una delle due impostazioni, dato che la conversione a grigio dallo spazio device è la stessa in entrambe le modalità

Trappole: /BC, /CS e la cache di rendering

Una soft mask di luminosità è coerente solo quanto il suo sfondo: il colore /BC che riempie l'area della mask fuori dal gruppo deve essere convertito con la stessa regola della pittura dentro il gruppo. HotPDF ha cambiato entrambi insieme nella v2.770.0. Convertire la pittura con RGB ponderato ma lo sfondo con CIE Y produce una cucitura visibile dove finisce il bounding box del gruppo, un bug facile da introdurre quando lo sfondo passa per un helper diverso dal contenuto

Gli attributi del gruppo devono anche essere validi prima che tutto ciò accada. HotPDF legge lo spazio di blending dalla voce /CS del gruppo e richiede che /BC porti esattamente un numero per componente di quello spazio: tre per uno spazio basato su RGB, quattro per CMYK, uno per il grigio. Una /CS mancante o una /BC con arità sbagliata fanno fallire la mask prima che il gruppo si esegua, e la pittura viene allora disegnata senza mask invece che mascherata con uno sfondo indovinato. Separation, DeviceN e NChannel non sono spazi di blending legali, quindi la pittura in colori spot dentro un gruppo mask passa per la sua tint transform verso lo spazio alternativo prima che la luminosità venga calcolata

Infine, l'impostazione cambia i pixel, quindi fa parte della chiave di cache. Le bitmap renderizzate con RenderColorimetricLuminosityMasks acceso e spento sono messe in cache separatamente sia nella cache di rendering in memoria sia su disco, e commutare la proprietà fra chiamate a THotPDF.RenderLoadedPageToBitmapCached non restituisce mai una bitmap dell'altra modalità. Una nota operativa: la v2.769.3 introdusse una regressione solo Win64 in cui le pagine con un gruppo di trasparenza che non impostava una soft mask propria non riuscivano a renderizzarsi; è corretta nella v2.770.1, quindi le applicazioni a 64 bit dovrebbero usare quella release o successiva

// Confronto affiancato: renderizzare la stessa pagina in entrambe le modalità.
// Ogni modalità è in cache con la sua chiave, quindi le chiamate ripetute costano poco.
Pdf.RenderColorimetricLuminosityMasks := False;
ViewerBmp := Pdf.RenderLoadedPageToBitmapCached(PageIndex, 96);

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

// ... mostrate entrambe, poi liberatele: le chiamate in cache restituiscono copie di proprietà del chiamante
ViewerBmp.Free;
StrictBmp.Free;

Per la API di rendering più ampia, inclusa la scelta della DPI, la cache di pagina e ciò che il renderer ancora approssima, vedere rendering di una pagina PDF in una TBitmap Delphi con HotPDF. Se il vostro problema è nella direzione opposta, togliere la trasparenza da un file per stampa o archiviazione invece di disegnarla, soft mask e flattening della trasparenza in PDFlibPas copre quel lato

FAQ

HotPDF renderizza correttamente i blend mode PDF dentro i gruppi di trasparenza?

Sì, dalla v2.769.3. Un gruppo dipinto con Multiply o un altro blend mode diverso da Normal fa il blend del proprio colore e della propria alpha con lo sfondo, e il suo contenuto parte da blending Normal come richiede ISO 32000-1 §11.6.6

Perché HotPDF renderizza due volte i gruppi non isolati?

Un gruppo non isolato non ha un piano alpha proprio. HotPDF lo disegna su nero e su bianco e recupera l'alpha come 1 − (white − black), il che costa un po' di velocità ma rende corrette le ombre Multiply

Conviene impostare RenderColorimetricLuminosityMasks a True?

Lasciatelo False per eguagliare ciò che mostrano i visualizzatori PDF comuni. Impostatelo True solo quando vi serve la lettura CIE Y di ISO 32000-1 per gruppi mask basati su CIE, per esempio in un confronto di conformità

I gruppi di trasparenza con blend mode, le soft mask di luminosità e il renderer di pagina che li disegna fanno parte del HotPDF Delphi Component, una libreria VCL nativa per Delphi e C++Builder che crea, modifica e renderizza documenti PDF senza DLL esterne