Technický článek

Blend skupiny a luminositní masky při vykreslování PDF

HotPDF vykresluje průhlednostní skupinu PDF malovanou ne-Normal blend módem tak, že nechá skupinu spočítat vlastní blend: Cb(1 − α) + α·B(Cb, Cs), s alfou skupiny, kterou sleduje, nebo obnoví z černého a bílého průchodu. Od v2.770.0 volí vlastnost THotPDF.RenderColorimetricLuminosityMasks taky, jestli luminositní soft masky čtou barvu jako vážené RGB, jako běžné prohlížeče, nebo jako CIE Y, jak popisuje ISO 32000-1

Obě změny vyšly z jednoho druhu souboru: stránka exportovaná z designového nástroje, kde produktová fotka sedí na měkkém vrženém stínu. Stín je Form XObject kreslený jako průhlednostní skupina s /BM /Multiply a jeho vyblednutí je luminositní soft maska. V jiných prohlížečích je stín světle neutrálně šedý. Ve starších buildech HotPDF vyšel téměř černý a zbarvený směrem k pozadí. Dvě samostatné chyby byly naskládané jedna na druhé a stojí za rozebrání, protože jakýkoli renderer, který vyroste z designu „nejdřív neprůhledné objekty“, narazí na obě

Proč se Multiply vržený stín vykresluje moc tmavě?

Multiply stín se vykresluje moc tmavě, když renderer blenduje složený výsledek skupiny místo vlastní barvy a alfy skupiny. Částečně průhledné pixely toho výsledku už pozadí obsahují, takže jejich blendování s pozadím vynásobí pozadí samo sebou. Normal blendování s neprůhledností je lineární a tuhle chybu nepozorovaně přežije; Multiply, Screen, Darken a každý jiný nelineární mód ne

Vykreslovací pipeline HotPDF původně řešila blend módy na každý malovaný objekt jednoduchým a rychlým modelem. Před namalováním objektu renderer udělá snapshot postižených pixelů; po malování porovná bitmapy „před“ a „po“ a bere barvu „po“ jako neprůhledný zdroj Cs, spočítá B(Cb, Cs) a složí ji s aktuální neprůhledností. Pro vyplněnou cestu, glyph nebo neprůhledný obrázek je to přesně správně: zdroj doopravdy je neprůhledný uvnitř svého tvaru a tvar je místo, kde se pixely změnily

Průhlednostní skupina ten předpoklad láme. ISO 32000-1 §11.4 definuje skupinu jako něco, co se nejdřív složí do vlastního výsledku, s vlastní barvou a vlastní alfou, a teprve pak se blenduje s pozadím jako jediný objekt. Tam, kde je stín neprůhledný na 30 procent, drží bitmapa „po“ zhruba 0,3 barvy stínu plus 0,7 stránky pod ní. Sypejte to do Multiply jako neprůhledný zdroj a vzorec se stane pozadí krát (hodnota, která už pozadí obsahuje): stín ztmavne a nabere odstín pozadí. Nic nespadne, žádná diagnostika se nezapne a stránka prostě vypadá špatně, což je pro renderer nejhorší způsob selhání

Jak HotPDF nechá průhlednostní skupinu blendovat vlastními silami

Od v2.769.3 stěhuje HotPDF blend skupiny malované pod ne-Normal blend módem z obecné cesty na objekt do samotného rendereru skupiny. Skupina spočítá Cb(1 − α) + α·B(Cb, Cs) přímo ze své barvy, své alfy a snapshotu pozadí pořízeného pro operátor Do, pak označí výsledek jako už blendovaný, takže vnější Do ho jen složí s aktuální neprůhledností místo blendovat podruhé. Obyčejné objekty si drží rychlý model před-po a skupiny pod Normal blendováním nejsou postižené, protože lineární případ nebyl nikdy špatně

Tvrdá část je alfa. Izolovaná skupina (/I true) startuje z úplně průhledného pozadí, takže renderer už pro ni sleduje samostatnou alfa rovinu a může α číst přímo. Neizolovaná skupina se skládá na cokoli, co je už na stránce, a žádnou vlastní alfa rovinu nemá. HotPDF ji obnoví tak, že skupinu nakreslí dvakrát, jednou na černou a jednou na bílou:

  • Na černé skončí každý pixel jako α·Cs
  • Na bílé skončí týž pixel jako α·Cs + (1 − α)
  • Odečtení dá α = 1 − (white − black) a pak Cs = black / α

Funguje to, protože každé Normal skládání, které renderer provede, včetně GDI AlphaBlend pro obrázky se soft maskami a vyhlazenými hranami, je lineární v pozadí. Trik má ale předpoklad: obsah skupiny musí sám blendovat normálně. Když vlastní ExtGState zdroje skupiny nastaví ne-Normal /BM, oba průchody se už neliší čistým lineárním členem, takže HotPDF skupinu nerozdělí a vrátí se k předchozímu chování. Kontrola pokrývá jen vlastní zdroje skupiny, ne vnořené formuláře. Cena je taky skutečná: takové stránky se renderují trochu pomaleji, protože se skupina kreslí dvakrát. Soft masky postavené uvnitř skupiny zůstávají, dokud oba průchody nedoběhnou, takže druhý průchod trefí do cache masek místo je stavět znovu

// Ilustrace obnovy alfy na kanál (hodnoty 0..1).
// Black = skupina nakreslená na černé pozadí, White = na bílé pozadí.
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;            // úplně průhledné: barva je irrelevantní
end;

// Multiply blend, pak skládání skupiny Cb(1 - a) + a * B(Cb, Cs)
function CompositeMultiply(Backdrop, Source, Alpha: Double): Double;
begin
  Result := Backdrop * (1.0 - Alpha) + Alpha * (Backdrop * Source);
end;

Kód výše je ilustrace té matematiky, ne HotPDF API; renderer dělá tutéž věc na kanál a na pixel uvnitř THotPDF.RenderLoadedPageToBitmap. V praxi alfa obnovená ze dvou 8bitových průchodů ztrácí přesnost, jak se blíží nule, takže velmi slabé hrany nesou nějaký kvantizační šum, proto dělení dává smysl jen tam, kde je alfa zjevně nad nulou

Blend mód se uvnitř skupiny resetuje na Normal

Blend mód, stejně jako konstantní alfa a soft maska, se vztahuje na průhlednostní skupinu jako celek, ne na každý objekt uvnitř ní, takže obsah skupiny musí začínat Normal blendováním (ISO 32000-1 §11.6.6). HotPDF dřív resetoval na vstupu do skupiny neprůhlednost a soft masku, ale blend mód nechal být. Výsledek uvnitř izolované skupiny byl dramatický: obsah se vynásobil sám do prázdného výchozího pozadí skupiny, které je černé, a celá skupina se vykreslila černě

Táž reset platí, když se staví soft maska. Pokud jedno ExtGState nastaví zároveň /BM /Multiply a /SMask, skupina masky se musí kreslit s Normal blendováním; jinak bílá skupina masky zčerná a skryje přesně to, co měla maska odhalit. Obě resety jsou součástí v2.769.3 a obě jsou na jednoduchých dokumentech neviditelné, proto žily tak dlouho, jak dlouho žily

Jakou hodnotu má mít luminositní soft maska pro poloviční šedou?

Podle ISO 32000-1 má luminositní soft maska, jejíž skupina používá CIE založený blendovací prostor, brát CIE 1931 Y a Y pro poloviční šedou sRGB 0.5 je jen asi 0.21, protože Y je lineární světlo. Běžné PDF prohlížeče místo toho rozřešou barvu skupiny do RGB a váží kódované hodnoty zhruba 0.30 / 0.59 / 0.11, což dá pro tutéž šedou asi 0.5. Od v2.770.0 je výchozí volbou HotPDF vážení kompatibilní s prohlížeči a pro čtení podle normy nabízí THotPDF.RenderColorimetricLuminosityMasks

Ta mezera není akademická. Vyblednutí v exportu z designového nástroje bývá gradient ve skupině ICCBased nebo kalibrované a polotóny jsou přesně tam, kde se lineární Y a gama kódovaná šedá rozejdou nejvíc. S CIE Y propustí maska poloviční šedé jen asi pětinu stínu místo poloviny, takže vržené stíny a viněty vylezou nápadně světlejší než v jakémkoli jiném prohlížeči, ve kterém si zákazník soubor otevře. Být správně podle písmene normy a špatně podle očí všech je špatný obchod pro výchozí nastavení, takže výchozí se drží prohlížečů a přísný mód je jeden příznak daleko

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('catalog-page.pdf') > 0 then
    begin
      // Výchozí (False): luminositní masky váží rozřešenou barvu
      // jako DeviceRGB, což odpovídá tomu, co ukazují běžné prohlížeče.
      // True: brát CIE Y, když má skupina masky CIE založený prostor.
      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;

Vlastnost hraje roli jen tehdy, když je blendovací prostor skupiny masky CIE založený: CalGray, CalRGB, Lab nebo ICCBased. Masky, jejichž skupina používá DeviceGray, DeviceRGB nebo DeviceCMYK, se pod oběma nastaveními renderují úplně jako dřív, protože konverze device prostoru do šedé je v obou módech stejná

Pasti: /BC, /CS a render cache

Luminositní soft maska je jen tak konzistentní jako její pozadí: barva /BC, která vyplňuje oblast masky mimo skupinu, se musí převádět stejným pravidlem jako malba uvnitř skupiny. HotPDF změnil obojí najednou ve v2.770.0. Převést malbu váženým RGB, ale pozadí CIE Y, vyprodukuje viditelný šev tam, kde končí bounding box skupiny — chyba snadná na zavedení, když pozadí jde přes jiný pomocník než obsah

Atributy skupiny musí být taky platné, než se cokoli z toho stane. HotPDF čte blendovací prostor z položky /CS skupiny a vyžaduje, aby /BC neslo přesně jedno číslo na komponentu toho prostoru: tři pro prostor na RGB, čtyři pro CMYK, jedno pro šedou. Chybějící /CS nebo /BC se špatným počtem hodnot dá masce selhat, ještě než se skupina spustí, a malba se pak kreslí bez masky místo maskovat se s uhádnutým pozadím. Separation, DeviceN a NChannel nejsou legální blendovací prostory, takže malba spot barvou uvnitř skupiny masky projde svou tint transformací do náhradního prostoru, než se spočítá luminosita

A konečně, tohle nastavení mění pixely, takže je součástí cache klíče. Bitmapy renderované s RenderColorimetricLuminosityMasks zapnutým a vypnutým se cachují odděleně v render cache v paměti i na disku a přepínání vlastnosti mezi voláními THotPDF.RenderLoadedPageToBitmapCached nikdy nevrátí bitmapu z druhého módu. Jedna provozní poznámka: v2.769.3 zavedla regresi jen pro Win64, ve které stránky s průhlednostní skupinou, která nemá vlastní soft masku, nešly vyrenderovat; opraveno je to ve v2.770.1, takže 64bitové aplikace mají brát tuhle verzi nebo novější

// Srovnání vedle sebe: vyrenderujte tutéž stránku v obou módech.
// Každý mód se cachuje pod vlastním klíčem, takže opakovaná volání jsou levná.
Pdf.RenderColorimetricLuminosityMasks := False;
ViewerBmp := Pdf.RenderLoadedPageToBitmapCached(PageIndex, 96);

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

// ... oba ukažte, pak uvolněte: cachovaná volání vracejí kopie vlastněné volajícím
ViewerBmp.Free;
StrictBmp.Free;

O širším renderovacím API, včetně volby DPI, cache stránek a toho, co renderer pořád aproximuje, píše vykreslení PDF stránky do Delphi TBitmap pomocí HotPDF. Pokud je váš problém opačným směrem — odstranit průhlednost ze souboru pro tisk nebo archivaci místo ji kreslit — tou stranou se zabývá soft masky a zplošťování průhlednosti v PDFlibPas

FAQ

Renderuje HotPDF blend módy PDF uvnitř průhlednostních skupin správně?

Ano, od v2.769.3. Skupina malovaná Multiply nebo jiným ne-Normal blend módem blenduje vlastní barvu a alfu s pozadím a její obsah začíná Normal blendováním, jak vyžaduje ISO 32000-1 §11.6.6

Proč renderuje HotPDF neizolované skupiny dvakrát?

Neizolovaná skupina nemá vlastní alfa rovinu. HotPDF ji nakreslí na černou a na bílou a obnoví alfu jako 1 − (white − black), což stojí trochu rychlosti, ale dělá Multiply stíny správnými

Mám nastavovat RenderColorimetricLuminosityMasks na True?

Nechte False, aby to odpovídalo tomu, co ukazují běžné PDF prohlížeče. Nastavte True jen tehdy, když potřebujete čtení CIE Y podle ISO 32000-1 pro CIE založené skupiny masek, třeba v srovnání shody s normou

Průhlednostní skupiny s blend módy, luminositní soft masky i renderer stránek, který je kreslí, jsou součástí HotPDF Delphi Component, nativní VCL knihovny pro Delphi a C++Builder, která vytváří, upravuje a renderuje PDF dokumenty bez externích DLL