Artykuł techniczny

Grupy trybów mieszania i maski luminancji w renderowaniu PDF

HotPDF renderuje grupę przezroczystości PDF malowaną trybem mieszania innym niż Normal, pozwalając grupie policzyć własny blend: Cb(1 − α) + α·B(Cb, Cs), z alfą grupy, którą śledzi albo odzyskuje z przebiegu na czerni i przebiegu na bieli. Od v2.770.0 właściwość THotPDF.RenderColorimetricLuminosityMasks wybiera też, czy miękkie maski luminancji czytają kolor jako ważone RGB, jak typowe przeglądarki, czy jako CIE Y, jak opisuje ISO 32000-1

Obie zmiany wyszły z tego samego rodzaju pliku: strony wyeksportowanej z narzędzia projektowego, gdzie zdjęcie produktu siedzi na miękkim rzuconym cieniu. Cień to Form XObject rysowany jako grupa przezroczystości z /BM /Multiply, a jego zanikanie to miękka maska luminancji. W innych przeglądarkach cień jest jasną, neutralną szarością. W starszych buildach HotPDF wychodził prawie czarny i podbarwiony kolorem tła. Dwa osobne błędy leżały jeden na drugim i warto je rozebrać, bo każdy renderer, który wyrasta z projektu "najpierw obiekty nieprzezroczyste", trafi na oba

Dlaczego cień Multiply renderuje się zbyt ciemno?

Cień Multiply renderuje się zbyt ciemno, gdy renderer blenduje zkompozytowany wynik grupy zamiast jej własnego koloru i alfy. Częściowo przezroczyste piksele tego wyniku zawierają już tło, więc blendowanie ich z tłem mnoży tło przez samo siebie. Zwykłe blendowanie z kryciem jest liniowe i przeżywa ten błąd niezauważony; Multiply, Screen, Darken i każdy inny nieliniowy tryb już nie

Potok renderowania HotPDF pierwotnie obsługiwał tryby mieszania per malowany obiekt prostym i szybkim modelem. Przed namalowaniem obiektu renderer robi snapshot dotkniętych pikseli; po namalowaniu porównuje bitmapy "przed" i "po" i traktuje kolor "po" jako nieprzezroczyste źródło Cs, licząc B(Cb, Cs) i kompozytując to z bieżącym kryciem. Dla wypełnionej ścieżki, glifa albo nieprzezroczystego obrazu to dokładnie dobrze: źródło naprawdę jest nieprzezroczyste wewnątrz swojego kształtu, a kształt to miejsce, gdzie piksele się zmieniły

Grupa przezroczystości łamie to założenie. ISO 32000-1 §11.4 definiuje grupę jako coś, co jest najpierw kompozytowane do własnego wyniku, z własnym kolorem i własną alfą, a dopiero potem blendowane z tłem jako jeden obiekt. Tam, gdzie cień ma 30 procent krycia, bitmapa "po" trzyma mniej więcej 0.3 koloru cienia plus 0.7 strony pod spodem. Wciśnij to w Multiply jako nieprzezroczyste źródło, a wzór stanie się tłem razy (wartość, która już zawiera tło): cień ciemnieje i łapie odcień tła. Nic nie pada, żadna diagnostyka się nie pali, a strona po prostu wygląda źle, co jest najgorszym sposobem zawodzenia renderera

Jak HotPDF pozwala grupie przezroczystości blendować samodzielnie

Od v2.769.3 HotPDF przenosi blend dla grupy malowanej pod trybem innym niż Normal z generycznej ścieżki per obiekt do samego renderera grupy. Grupa liczy Cb(1 − α) + α·B(Cb, Cs) wprost ze swojego koloru, swojej alfy i snapshotu tła zrobionego dla operatora Do, a potem oznacza wynik jako już zblendowany, więc otaczający Do tylko kompozytuje go z bieżącym kryciem zamiast blendować drugi raz. Zwykłe obiekty trzymają szybki model przed-po, a grupy pod zwykłym blendowaniem są nietknięte, bo przypadek liniowy nigdy nie był zły

Trudną częścią jest alfa. Grupa izolowana (/I true) startuje od całkiem przezroczystego tła, więc renderer prowadzi dla niej osobną płaszczyznę alfy i może czytać α wprost. Grupa nieizolowana kompozytuje się na to, co już jest na stronie, i nie ma własnej płaszczyzny alfy. HotPDF odzyskuje ją, rysując grupę dwa razy, raz na czerni i raz na bieli:

  • Na czerni każdy piksel kończy jako α·Cs
  • Na bieli ten sam piksel kończy jako α·Cs + (1 − α)
  • Odejmowanie daje α = 1 − (white − black), a potem Cs = black / α

To działa, bo każdy zwykły kompozyt, jaki renderer wykonuje, włącznie z GDI AlphaBlend dla obrazów z miękkimi maskami i antyaliasowanymi krawędziami, jest liniowy względem tła. Trik ma jednak warunek wstępny: sama treść grupy musi blendować normalnie. Jeśli własne zasoby ExtGState grupy ustawiają nieNormalne /BM, dwa przebiegi nie różnią się już czystym liniowym składnikiem, więc HotPDF nie dzieli grupy i cofa się do poprzedniego zachowania. Sprawdzenie pokrywa tylko własne zasoby grupy, nie zagnieżdżone formy. Koszt jest też realny: takie strony renderują się nieco wolniej, bo grupa jest rysowana dwa razy. Miękkie maski zbudowane wewnątrz grupy są trzymane, dopóki oba przebiegi się nie skończą, więc drugi przebieg trafia w cache masek zamiast ich przebudowywać

// Ilustracja odzyskiwania alfy na kanał (wartości 0..1).
// Black = grupa namalowana na czarnym tle, White = na białym tle.
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   // odpremnożenie
  else
    Source := 0.0;            // całkiem przezroczysty: kolor obojętny
end;

// Blend Multiply, potem kompozyt grupy Cb(1 - a) + a * B(Cb, Cs)
function CompositeMultiply(Backdrop, Source, Alpha: Double): Double;
begin
  Result := Backdrop * (1.0 - Alpha) + Alpha * (Backdrop * Source);
end;

Kod powyżej to ilustracja matematyki, nie API HotPDF; renderer robi to samo na kanał i na piksel wewnątrz THotPDF.RenderLoadedPageToBitmap. W praktyce alfa odzyskana z dwóch przebiegów 8-bit traci precyzję, gdy zbliża się do zera, więc bardzo blade krawędzie niosą trochę szumu kwantyzacji, dlatego dzielenie ma sens tylko tam, gdzie alfa jest wyraźnie nad zerem

Tryb mieszania resetuje się do Normal wewnątrz grupy

Tryb mieszania, jak stała alfa i miękka maska, dotyczy grupy przezroczystości jako całości, a nie każdego obiektu w jej środku, więc treść grupy musi zaczynać od zwykłego blendowania (ISO 32000-1 §11.6.6). HotPDF wcześniej resetował krycie i miękką maskę przy wejściu w grupę, ale tryb mieszania zostawiał w spokoju. Wynik wewnątrz grupy izolowanej był dramatyczny: treść mnożyła sama siebie na pustym początkowym tle grupy, które jest czarne, i cała grupa malowała się na czarno

Ten sam reset działa, gdy budowana jest miękka maska. Jeśli jedno ExtGState ustawia i /BM /Multiply, i /SMask, grupa maski musi być rysowana ze zwykłym blendowaniem; inaczej biała grupa maski staje się czarna i chowa dokładnie to, co maska miała odsłonić. Oba resety są częścią v2.769.3 i oba są niewidoczne na prostych dokumentach, dlatego żyły tak długo, jak żyły

Jaką wartość powinna brać miękka maska luminancji dla półszarości?

Według ISO 32000-1 miękka maska luminancji, której grupa używa przestrzeni blendowania opartej na CIE, powinna brać CIE 1931 Y, a Y dla półszarości sRGB równej 0.5 to tylko około 0.21, bo Y jest światłem liniowym. Typowe przeglądarki PDF rozwiązują zamiast tego kolor grupy do RGB i ważą zakodowane wartości mniej więcej 0.30 / 0.59 / 0.11, co daje około 0.5 dla tej samej szarości. Od v2.770.0 HotPDF domyślnie stosuje ważenie zgodne z przeglądarkami i oferuje THotPDF.RenderColorimetricLuminosityMasks dla lektury zgodnej ze specyfikacją

Rozjazd nie jest akademicki. Zanikanie w eksporcie z narzędzia projektowego to zwykle gradient w grupie ICCBased albo skalibrowanej, a półtony to dokładnie miejsce, gdzie liniowe Y i gamma-zakodowana szarość rozchodzą się najbardziej. Z CIE Y maska półszarości przepuszcza tylko około jednej piątej cienia zamiast połowy, więc rzucone cienie i winiety wychodzą wyraźnie jaśniejsze niż w każdej innej przeglądarce, w którą klient otworzy plik. Być w porządku według litery standardu i w kontra według oczu wszystkich to zły układ dla domyślnej wartości, więc domyślna idzie za przeglądarkami, a tryb ścisły jest jedną właściwością stąd

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('catalog-page.pdf') > 0 then
    begin
      // Domyślnie (False): maski luminancji ważą rozwiązany kolor
      // jak DeviceRGB, zgodnie z tym, co pokazują typowe przeglądarki.
      // True: bierz CIE Y, gdy grupa maski ma przestrzeń opartą na 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;

Właściwość ma znaczenie tylko wtedy, gdy przestrzeń blendowania grupy maski jest oparta na CIE: CalGray, CalRGB, Lab albo ICCBased. Maski, których grupa używa DeviceGray, DeviceRGB albo DeviceCMYK, renderują się dokładnie jak wcześniej przy obu ustawieniach, bo konwersja przestrzeni urządzeniowej do szarości jest w obu trybach ta sama

Pułapki: /BC, /CS i cache renderowania

Miękka maska luminancji jest tak spójna, jak jej tło: kolor /BC, który wypełnia obszar maski poza grupą, musi być konwertowany tą samą regułą co malowanie wewnątrz grupy. HotPDF zmienił oba razem w v2.770.0. Konwersja malowania ważonym RGB, a tła CIE Y, produkuje widoczny szew tam, gdzie kończy się bounding box grupy, błąd łatwy do wprowadzenia, gdy tło idzie przez inną funkcję pomocniczą niż treść

Atrybuty grupy muszą też być poprawne, zanim cokolwiek z tego ruszy. HotPDF czyta przestrzeń blendowania z wpisu /CS grupy i wymaga, by /BC niósł dokładnie jedną liczbę na komponent tej przestrzeni: trzy dla przestrzeni opartej na RGB, cztery dla CMYK, jeden dla szarości. Brakujący /CS albo /BC o złej arności wywraca maskę, zanim grupa się wykona, i malowanie jest wtedy rysowane bez maski, zamiast być maskowane zgadywanym tłem. Separation, DeviceN i NChannel nie są legalnymi przestrzeniami blendowania, więc malowanie kolorem spot wewnątrz grupy maski przechodzi przez swoją transformację tint do przestrzeni zastępczej, zanim policzona zostanie luminancja

Wreszcie, ustawienie zmienia piksele, więc jest częścią klucza cache. Bitmapy renderowane z RenderColorimetricLuminosityMasks włączonym i wyłączonym są cache'owane osobno w cache renderowania w pamięci i na dysku, a przełączanie właściwości między wywołaniami THotPDF.RenderLoadedPageToBitmapCached nigdy nie zwraca bitmapy z drugiego trybu. Jedna uwaga operacyjna: v2.769.3 wprowadziło regresję tylko na Win64, w której strony z grupą przezroczystości nieustawiającą własnej miękkiej maski nie chciały się renderować; poprawione w v2.770.1, więc aplikacje 64-bit powinny używać tego wydania albo nowszego

// Proofing obok siebie: renderuj tę samą stronę w obu trybach.
// Każdy tryb jest cache'owany pod własnym kluczem, więc powtórne wywołania są tanie.
Pdf.RenderColorimetricLuminosityMasks := False;
ViewerBmp := Pdf.RenderLoadedPageToBitmapCached(PageIndex, 96);

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

// ... pokaż oba, potem zwolnij: cache'owane wywołania zwracają kopie własności wołającego
ViewerBmp.Free;
StrictBmp.Free;

Szersze API renderowania, włącznie z wyborem DPI, cache stron i tym, czego renderer nadal nie robi dokładnie, opisuje renderowanie strony PDF do Delphi TBitmap w HotPDF. Jeśli twój problem idzie w przeciwną stronę, czyli usuwanie przezroczystości z pliku do druku albo archiwum zamiast jej rysowania, miękkie maski i spłaszczanie przezroczystości w PDFlibPas pokrywa tę stronę

FAQ

Czy HotPDF renderuje tryby mieszania PDF wewnątrz grup przezroczystości poprawnie?

Tak, od v2.769.3. Grupa malowana Multiply albo innym trybem innym niż Normal blenduje własny kolor i alfę z tłem, a jej treść zaczyna od zwykłego blendowania, jak wymaga ISO 32000-1 §11.6.6

Dlaczego HotPDF renderuje grupy nieizolowane dwa razy?

Grupa nieizolowana nie ma własnej płaszczyzny alfy. HotPDF rysuje ją na czerni i na bieli i odzyskuje alfę jako 1 − (white − black), co kosztuje trochę szybkości, ale robi cienie Multiply poprawnymi

Czy ustawić RenderColorimetricLuminosityMasks na True?

Zostaw False, żeby zgadzać się z tym, co pokazują typowe przeglądarki PDF. Ustaw True tylko, gdy potrzebujesz lektury CIE Y z ISO 32000-1 dla grup masek opartych na CIE, na przykład w porównaniu zgodności

Grupy przezroczystości z trybami mieszania, miękkie maski luminancji i renderer stron, który je rysuje, są częścią HotPDF Delphi Component, natywnej biblioteki VCL dla Delphi i C++Builder, która tworzy, edytuje i renderuje dokumenty PDF bez zewnętrznych DLL