Artykuł techniczny

Konwersja PDF do jednej przestrzeni kolorów w Delphi

Nagłówek wypełniony 1 0 0 rg, czystą czerwienią, wraca z konwersji do szarości jako 0.299 g. Czysty niebieski ląduje na 0.114 g, wystarczająco blisko czerni, że granatowe logo i tekst główny stają się trudne do odróżnienia. Te liczby nie są wypadkiem zaokrąglenia: to wagi luminancji BT.601, te same, które czytnik stosuje, gdy pokazuje stronę RGB na urządzeniu w skali szarości, i one decydują, ile kontrastu przetrwa, gdy kolorowa broszura musi stać się szarą

Co RecolorDocument liczy dla każdego koloru

PDF Library for Delphi wystawia konwersję jako RecolorDocument(TargetSpace) na TPDFlib, z 1 dla szarości, 2 dla RGB i 3 dla CMYK. Zwraca liczbę przekonwertowanych rzeczy, a geometria, pozycje tekstu i rozmieszczenie obrazów zostają tam, gdzie były. Zmieniają się tylko wartości kolorów i nazwy przestrzeni kolorów wokół nich

Każdy kolor przechodzi przez RGB w drodze do celu. Szarość to suma luminancji 0.299 R + 0.587 G + 0.114 B. CMYK dociera do RGB prostą regułą urządzeniową z ISO 32000-1 §10.3, gdzie każdy kanał to jeden minus suma farby i czerni, przycięta do zera. RGB dociera do CMYK przez wciągnięcie największej składowej do czerni: K = 1 - max(R, G, B), a pozostałe farby są skalowane z tego, co zostało. Kilka wyników, które warto znać, zanim cokolwiek pójdzie do drukarni:

  • Szarość 0.4 g skonwertowana do CMYK staje się 0 0 0 0.6 k, sama czerń, czyli to, czego drukarnia chce dla tekstu i linii
  • RGB 1 0.5 0 (pomarańcz) staje się 0 0.5 1 0 k, bez żadnej czerni
  • Bogata czerń taka jak 0.6 0.4 0.4 1 k staje się 0 g w drodze do szarości, więc dodatkowe farby po prostu znikają
  • Czysty cyjan, 1 0 0 0 k, zamienia się w 0.701 g, jaśniejszy, niż większość ludzi oczekuje od nasyconej farby

Przekonwertowane operandy są zapisywane z co najwyżej trzema miejscami po przecinku i zawsze z kropką jako separatorem dziesiętnym, niezależnie od ustawień regionalnych maszyny, na której działa zadanie. ISO 32000-1 §7.3.3 nie pozwala na nic innego w strumieniu treści, a konwerter formatujący liczby wg systemowych ustawień regionalnych zapisze 0,5 g na niemieckiej stacji roboczej, co czytnik parsuje jako liczbę 0 i nieznany operator

var
  Lib: TPDFlib;
  Converted: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('brochure.pdf', '') <> 1 then
      raise Exception.Create('Could not open brochure.pdf');
    Converted := Lib.RecolorDocument(1);   // 1 = szarość, 2 = RGB, 3 = CMYK
    WriteLn(Format('%d colors, images and gradients converted', [Converted]));
    Lib.SaveToFile('brochure-gray.pdf');
  finally
    Lib.Free;
  end;
end;

Strona, której kolory są już w przestrzeni docelowej, zachowuje swoje pierwotne bajty treści. RecolorDocument liczy tylko konwersje, które faktycznie przechodzą z jednej przestrzeni do drugiej, więc już szary dokument raportuje 0 i nic w nim nie jest przepisywane. Na dużym pliku to ma znaczenie: diff wejścia i wyjścia pokazuje dokładnie strony, które się zmieniły

Dlaczego RecolorPage zostawia zdjęcie z okładki w kolorze?

RecolorPage konwertuje operatory koloru na wybranej stronie i na tym się kończy. Obrazy i gradienty to obiekty poziomu dokumentu, często współdzielone przez kilka stron, i konwertuje je tylko RecolorDocument. Więc oczywisty sposób konwersji samej okładki — wybierz stronę 1 i zawołaj RecolorPage(3) — daje ci nagłówki i linie w CMYK nad fotograficznym RGB

Ten podział jest celowy. Obrazowy XObject rysowany na okładce może być rysowany także na stronie 12, i konwersja go dla jednej strony konwertuje go dla obu. Wywołanie o zasięgu strony, które po cichu sięgnęłoby do współdzielonych zasobów, zmieniłoby strony, których wołający nigdy nie wybrał. To jest gorsza niespodzianka z tej dwójki, więc wywołanie per strona dotyka tylko tego, co należy do strony

// Operatory tylko na okładce; obrazy i gradienty nie są tu konwertowane
Lib.SelectPage(1);
Changed := Lib.RecolorPage(3);
if Changed = 0 then
  WriteLn('Cover already uses CMYK operators (or the target was out of range)');
Lib.SaveToFile('cover-cmyk-operators.pdf');

// Dla dokumentu, który ma być w jednej przestrzeni w całości, skonwertuj wszystko
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');

Patrz na wartość zwracaną w tym fragmencie. Oba wywołania zwracają 0 dla celu spoza zakresu 1 do 3, a 0 to też odpowiedź dla strony, która nie potrzebowała zmiany, więc literówka w celu wygląda dokładnie jak dokument, który już był poprawny. Zweryfikuj argument samodzielnie przed wywołaniem. RecolorDocument przywraca stronę wybraną przed swoim startem, więc możesz zawołać go w środku zadania strona po stronie bez ponownego wybierania

Jeszcze jeden szczegół zasięgu. Przebieg operatorów czyta własny strumień treści każdej strony — instrukcje rysowania wypisane na samej stronie — i konwertuje g, rg, k, ich formy kreskowania oraz sc / scn, gdy bieżąca przestrzeń jest przestrzenią urządzeniową. Gdy przed konwertowanym sc stoi /DeviceRGB cs, nazwa przestrzeni też jest przepisywana na cel. Pomiń to przepisanie w ręcznie pisanym konwerterze, a czytnik zostaje z szukaniem trzech składowych RGB tam, gdzie siedzi teraz jedna wartość szarości

Obrazy i palety: które się zmieniają, a które zachowują bajty

Obrazy znaczą tyle co operatory. Broszura skonwertowana do szarości z obsługą samych operatorów nadal pokazuje kolorowe fotografie na szarych stronach, co czyta się gorzej niż oryginał i gorzej niż pełna konwersja. RecolorDocument konwertuje obraz, gdy ten ma osiem bitów na składową, przestrzeń kolorów urządzenia oraz brak kompresji albo zwykły Flate bez parametrów dekodowania

Obrazy indeksowane jadą inną drogą. Ich próbki to indeksy do tabeli kolorów, więc konwertowana jest tabela, a każda próbka zachowuje indeks, który już trzymała. Zrzut ekranu albo wykres zapisany jako obraz z 256-kolorową paletą konwertuje się tą drogą niezależnie od tego, jak jego próbki są pakowane czy kompresowane, bo próbki nigdy nie są dekodowane. Podstawa palety musi być przestrzenią urządzeniową; paleta nad profilem ICC zachowuje swoją tabelę

Diagram pokazujący, które części PDF RecolorDocument przepisuje w Delphi, a które części, jak obrazy JPEG i miękkie maski, przechodzą bajt w bajt identycznie
Przebieg RecolorDocument przepisuje operatory, kwalifikujące się obrazy, palety indeksowane i pary stopni gradientu, podczas gdy bajty JPEG, maski i kolory związane z profilem jadą nietknięte

Obraz zakodowany jako JPEG albo fax zachowuje swoje bajty. Zmiana jego kolorów oznaczałaby dekodowanie, konwersję i ponowną kompresję, co dodaje drugą generację artefaktów JPEG i zmienia rozmiar pliku w sposób, o jaki nikt nie prosił. Jeśli zadanie naprawdę potrzebuje każdego zdjęcia w szarości, ponowne kodowanie to osobna decyzja z własnym ustawieniem jakości, i wolimy, żeby wołający podjął ją jawnie, niż żeby konwersja koloru podjęła ją za niego

Miękkie maski i masky szablonowe zostają takie, jakie są, z innego powodu. Miękka maska to zwykle obraz DeviceGray, ale jej wartości znaczą pokrycie. Konwersja do trójskładnikowego RGB sprawia, że viewer przestaje traktować ją jako przezroczystość; kończy namalowana jako szara grafika na wierzchu treści, którą miała kształtować. RecolorDocument zbiera każdy obiekt referencjonowany przez /SMask albo /Mask, zanim skonwertuje jakikolwiek obraz, a obraz z /ImageMask true nie ma koloru do konwersji w ogóle

Kolory ustawione przez przestrzenie ICCBased, Separation, Indexed albo Pattern na poziomie operatorów też zostają zapisane tak, jak są. Zamiana koloru spot albo wartości oznaczonej profilem na szarość urządzenia bez przeczytania profilu zmieniłaby wygląd strony, a przebieg, którego robotą jest zachowanie wyglądu, nie może zacząć zgadywać, co profil miał na myśli

Gradienty: przestrzeń kolorów i stopnie ruszają razem

Shading deklaruje swoją przestrzeń kolorów raz i oddaje każdy stopień funkcji. Przepisz /ColorSpace z DeviceRGB na DeviceGray, podczas gdy funkcja nadal zwraca trzy liczby, a czytnik weźmie z nich pierwszą, kanał czerwony, jako poziom szarości. Gradient z niebieskiego do białego zrenderuje się wtedy jako pasmo zaczynające się od czerni. RecolorDocument najpierw obchodzi całe drzewo funkcji tylko do odczytu i niczego nie konwertuje, chyba że każdy węzeł da się skonwertować; dopiero wtedy przepisuje stopnie i przepisuje nazwę przestrzeni

Funkcje, które przepisuje, to typ wykładniczy, którego dwa kolory końcowe /C0 i /C1 są zwykłymi tablicami, oraz funkcje stitching z nich zbudowane. Kolor /Background jest konwertowany razem z resztą. Shading zdefiniowany próbkowaną tabelą albo programem PostScript zachowuje swoją definicję, podobnie jak typy mesh od 4 do 7, które przechowują kolory wewnątrz strumienia zamiast w funkcji. Przepisanie czegokolwiek z tego to regeneracja, inna robota niż konwersja koloru

Diagram PDF Library for Delphi konwersji gradientów łączący przestrzeń kolorów shadingu z jego funkcją stopni w obiektach samodzielnych, wzorcach i zasobach shadingu strony, podczas gdy shadingi próbkowane, PostScript i mesh zachowują własną definicję
Konwersja gradientu znaczy przeniesienie jego przestrzeni kolorów i stopni za nim razem, gdziekolwiek shading mieszka, a shade'y mesh albo z funkcją próbkowaną zachowują zamiast tego własną definicję

Znajdowanie gradientów to własna pułapka. Narzędzia projektowe rzadko dają shadingowi obiekt jego własny; zwykle siedzi inline wewnątrz słownika wzorca, który go maluje, albo wewnątrz zasobów /Shading strony. Gdy budowaliśmy przebieg gradientów, skanowanie obiektów najwyższego poziomu znalazło ich prawie żadne. Wyszukiwanie obchodzi teraz graf obiektów, ograniczone do dwunastu poziomów w głąb i odwiedzające każdy numer obiektu raz, więc zasób współdzielony przez wszystkie strony jest konwertowany raz, a plik wskazujący obiekt na samego siebie nie może się zapętlić

Gdzie rekolorowanie pasuje przed zgłoszeniem do drukarni

Traktuj RecolorDocument jako normalizację. Konwertuje wartości między przestrzeniami urządzeń; nie stosuje profilu ICC, nie respektuje rendering intent ani nie symuluje warunków druku. Dla zadania, które musi trafić w próbkę umowną, właściwym narzędziem jest workflow napędzany profilem, a przebieg rekolorowania to co najwyżej krok, który sprowadza wszystko do jednej przestrzeni, zanim ten workflow ruszy

Tam, gdzie zarabia swoją chleb, to codzienna produkcja: zgłoszenie, które ma być szare, a przychodzi z logo w RGB, raport wewnętrzny, który nie może się drukować w kolorze, wsad, który krok preflight odrzuci, dopóki każdy plik nie użyje jednej przestrzeni. Cokolwiek wychodzi z budynku powinno przejść przez RecolorDocument, nigdy samą RecolorPage, bo półskonwertowany plik przechodzi szybki podgląd wzrokowy i wywala się u drukarza. Werdykt uruchom w tym samym zadaniu: notatki o preflight PDF/A i PDF/UA i o warsztacie zgodności i podpisywania pokazują jak

// IsValidRecolorTarget to helper, który dopiszesz sam; biblioteka nie ma takiego wywołania
function IsValidRecolorTarget(Space: Integer): Boolean;
begin
  Result := (Space >= 1) and (Space <= 3);
end;

procedure NormalizeFile(const Src, Dst: string; Space: Integer);
var
  Lib: TPDFlib;
begin
  if not IsValidRecolorTarget(Space) then
    raise EArgumentException.CreateFmt('Bad recolor target %d', [Space]);
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile(Src, '') <> 1 then
      raise Exception.CreateFmt('Could not open %s', [Src]);
    if Lib.RecolorDocument(Space) = 0 then
      WriteLn(Src, ': already in the target space, copied unchanged');
    Lib.SaveToFile(Dst);
  finally
    Lib.Free;
  end;
end;

Szybkie sprawdzenie, które możesz zrobić na wyjściu: otwórz treść skonwertowanej strony w dowolnym inspektorze PDF po konwersji do szarości. Własna treść strony nie powinna już zawierać żadnych operatorów rg, RG, k ani K. Kolor, który przeżyje przebieg, pochodzi z przypadków powyżej: JPEG, kolor oznaczony profilem albo spot, gradient próbkowany albo mesh, albo grafika rysowana wewnątrz form XObject, której instrukcje mieszkają we własnym strumieniu, a nie w treści strony

Jeśli budujesz też grafikę wektorową, a nie tylko ją konwertujesz, przewodnik po grafice wektorowej, shaderach i gradientach pokazuje, jak shadingi i wzorce powstają w pierwszej kolejności, co czyni reguły gradientów powyżej łatwiejszymi do przewidzenia

Przebieg kolorów działa w procesie: PDF Library for Delphi to natywny Pascal dla Delphi, C++Buildera i Lazarusa, więc normalizacja wsadu przychodzących plików nie potrzebuje zewnętrznego konwertera ani narzędzia wiersza poleceń na serwerze. Strona produktu PDF Library for Delphi wylicza resztę API przetwarzania dokumentów