Заголовок, залитый 1 0 0 rg — чистым красным, — возвращается из серой конверсии как 0.299 g. Чистый синий приземляется на 0.114 g, достаточно близко к чёрному, чтобы тёмно-синий логотип и основной текст стало трудно различить. Эти числа не случайность округления: это люминансные веса BT.601, те же, что вьюер применяет, показывая RGB-страницу на сером устройстве, и они решают, сколько контраста переживёт превращение цветного буклета в серый
Что RecolorDocument вычисляет для каждого цвета
PDF Library for Delphi предоставляет конверсию как RecolorDocument(TargetSpace) на TPDFlib, где 1 — серое, 2 — RGB, 3 — CMYK. Она возвращает число преобразованного, а геометрия, позиции текста и размещение изображений остаются где были. Меняются только цветовые значения и имена цветовых пространств вокруг них
Каждый цвет по дороге к цели проходит через RGB. Серое — люминансная сумма 0.299 R + 0.587 G + 0.114 B. CMYK добирается до RGB по простому аппаратному правилу из ISO 32000-1 §10.3, где каждый канал — единица минус сумма его краски и чёрного, зажатая в ноль. RGB добирается до CMYK вытягиванием наибольшей компоненты в чёрный: K = 1 - max(R, G, B), а оставшиеся краски масштабируются из остатка. Несколько результатов, которые стоит знать, прежде чем отправлять что-либо в печать:
- Серое
0.4 gпри конверсии в CMYK становится0 0 0 0.6 k— только чёрная краска, чего печатная машина и хочет для текста и линеек - RGB
1 0.5 0(оранжевый) становится0 0.5 1 0 k, вовсе без чёрного - Составной чёрный вроде
0.6 0.4 0.4 1 kпо дороге в серое становится0 g, так что лишние краски просто исчезают - Чистый циан,
1 0 0 0 k, превращается в0.701 g— светлее, чем большинство ожидает от насыщенной краски
Преобразованные операнды пишутся максимум с тремя знаками после запятой и всегда с точкой в качестве десятичного разделителя, каков бы ни был локал машины, выполняющей задание. ISO 32000-1 §7.3.3 не допускает в потоке содержимого ничего другого, и конвертер, форматирующий числа по системному локалу, напишет на немецкой рабочей станции 0,5 g, что ридер разберёт как число 0 с неизвестным оператором следом
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 = серый, 2 = RGB, 3 = CMYK
WriteLn(Format('%d colors, images and gradients converted', [Converted]));
Lib.SaveToFile('brochure-gray.pdf');
finally
Lib.Free;
end;
end;
Страница, чьи цвета уже в целевом пространстве, хранит исходные байты содержимого. RecolorDocument считает только конверсии, реально пересекающие из одного пространства в другое, так что документ, уже серый, отчитывает 0, и в нём ничего не переписывается. На большом файле это важно: diff входа и выхода показывает ровно те страницы, что изменились
Почему RecolorPage оставляет фото обложки в цвете?
RecolorPage преобразует цветовые операторы выбранной страницы и останавливается на этом. Изображения и градиенты — объекты уровня документа, часто разделяемые несколькими страницами, и только RecolorDocument их преобразует. Так что очевидный способ преобразовать одну обложку — выбрать страницу 1 и вызвать RecolorPage(3) — даст вам CMYK-заголовки и линейки поверх RGB-фотографии
Разделение намеренное. Image XObject, нарисованный на обложке, может рисоваться и на странице 12, и преобразование его ради одной страницы преобразует его для обеих. Вызов со страницей в охвате, тихо залезающий в общие ресурсы, поменял бы страницы, которые вызывающий не выбирал. Это худшая из двух неожиданностей, поэтому постраничный вызов трогает только то, что принадлежит странице
// Операторы только на обложке; изображения и градиенты здесь не преобразуются
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');
// Для документа, который должен быть весь в одном пространстве, преобразуйте всё
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');
Следите за возвращаемым значением в том фрагменте. Оба вызова возвращают 0 для цели вне 1–3, и 0 же — ответ страницы, которой не нужно изменений, так что опечатка в цели выглядит в точности как документ, уже правильный. Валидируйте аргумент сами перед вызовом. RecolorDocument возвращает выбранной ту страницу, что была выбрана до его запуска, значит, можно вызвать его посреди постраничного задания без повторного выбора после
Ещё одна деталь охвата. Проход по операторам читает собственный поток содержимого каждой страницы — инструкции рисования, перечисленные на самой странице, — и преобразует g, rg, k, их формы для обводки, а также sc / scn, когда текущее пространство аппаратное. Когда /DeviceRGB cs стоит перед преобразуемым sc, имя пространства тоже переписывается в целевое. Пропустите то переименование в самописном конвертере — и ридер останется искать три компоненты RGB там, где теперь сидит одно серое значение
Изображения и палитры: какие меняются, какие хранят свои байты
Изображения важны не меньше операторов. Буклет, переведённый в серое с обработкой одних лишь операторов, всё ещё показывает цветные фотографии на серых страницах, что читается хуже и оригинала, и полной конверсии. RecolorDocument преобразует изображение, когда у того восемь бит на компонент, аппаратное цветовое пространство и либо отсутствие сжатия, либо обычный Flate без decode-параметров
Индексные изображения идут другим путём. Их сэмплы — индексы в цветовую таблицу, поэтому таблица преобразуется, а каждый сэмпл сохраняет индекс, который уже имел. Скриншот или диаграмма, сохранённые палитровым изображением в 256 цветов, преобразуются так независимо от того, как упакованы или сжаты их сэмплы, потому что сэмплы никогда не декодируются. База палитры должна быть аппаратным пространством; палитра поверх ICC-профиля хранит свою таблицу
JPEG или факсимильно-кодированное изображение хранит свои байты. Смена его цветов означала бы декодирование, преобразование и рекомпрессию, что добавляет второе поколение JPEG-артефактов и меняет размер файла способами, о которых никто не просил. Если заданию действительно нужны все фотографии в сером, перекодирование — отдельное решение со своей настройкой качества, и мы предпочитаем, чтобы вызывающий принял его явно, а не чтобы цветовая конверсия приняла за него
Мягкие и трафаретные маски остаются как есть по другой причине. Мягкая маска обычно изображение DeviceGray, но её значения означают покрытие. Преобразуйте её в трёхкомпонентный RGB, и вьюер перестаёт трактовать её как прозрачность; в итоге она оказывается нарисованной серой графикой поверх содержимого, форму которого должна была задавать. RecolorDocument собирает все объекты, на которые ссылаются /SMask или /Mask, прежде чем преобразовать хоть одно изображение, а изображение с /ImageMask true вообще не имеет цвета для преобразования
Цвета, заданные на уровне операторов через пространства ICCBased, Separation, Indexed или Pattern, тоже остаются как написаны. Превращение плашечного цвета или значения с профилем в аппаратное серое без чтения профиля изменило бы вид страницы, а проход, чья работа — сохранять вид, не может начинать гадать, что имел в виду профиль
Градиенты: цветовое пространство и точки переезжают вместе
Шейдинг объявляет своё цветовое пространство однажды и вручает каждую точку функции. Переименуйте /ColorSpace из DeviceRGB в DeviceGray, пока функция всё ещё возвращает три числа, и ридер примет первое из них — красный канал — за уровень серого. Градиент из синего в белый тогда отрисуется полосой, начинающейся с чёрного. RecolorDocument сперва обходит всё дерево функции в режиме чтения и ничего не преобразует, пока каждый его узел способен преобразоваться; лишь затем он переписывает точки и переименовывает пространство
Функции, которые он переписывает, — экспоненциальный тип, чьи два конечных цвета /C0 и /C1 — простые массивы, — и сшивающие функции, построенные из них. Цвет /Background преобразуется вместе со всеми. Шейдинг, определённый табулированной таблицей или PostScript-программой, хранит своё определение, как и mesh-типы 4–7, которые держат цвета внутри потока вместо функции. Переписать и то и другое — регенерация, работа иная, чем преобразование цвета
Поиск градиентов — своя ловушка. Дизайнерские инструменты редко дают шейдингу отдельный объект; обычно он сидит инлайн внутри словаря паттерна, который его рисует, или внутри ресурсов /Shading страницы. Когда мы строили градиентный проход, скан объектов верхнего уровня почти не находил их. Поиск теперь идёт по графу объектов, ограниченный двенадцатью уровнями глубины и посещающий каждый номер объекта однажды, так что ресурс, разделяемый всеми страницами, преобразуется однажды, а файл, указывающий объектом на самого себя, не может зациклиться
Где перекраска уместна перед подачей в печать
Относитесь к RecolorDocument как к нормализации. Он преобразует значения между аппаратными пространствами; не применяет ICC-профиль, не учитывает интент воспроизведения и не имитирует условия печатной машины. Для работы, которая должна совпасть с контрактной цветопробой, правильный инструмент — процесс под управлением профиля, а проход перекраски — максимум шаг, собирающий всё в одно пространство до запуска того процесса
Где он себя окупает — так это в повседневном производственном случае: подача, которая должна быть серой и приходит с RGB-логотипом, внутренний отчёт, которому нельзя печататься в цвете, пакет, который шаг предпечатной проверки отвергнет, пока каждый файл не перейдёт на одно пространство. Всё, что покидает здание, должно проходить через RecolorDocument, никогда один лишь RecolorPage, потому что полупреобразованный файл проходит быструю визуальную проверку и потом проваливается у печатной машины. Прогоняйте вердикт в том же задании: заметки о профилировании PDF/A и PDF/UA и о верстаке соответствия и подписания показывают как
// IsValidRecolorTarget — хелпер, который вы добавляете сами; у библиотеки такого вызова нет
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;
Быстрая проверка, которую можно прогнать на выходе: откройте содержимое преобразованной страницы в любом PDF-инспекторе после серой конверсии. Собственное содержимое страницы больше не должно содержать операторов rg, RG, k или K. Цвет, переживший проход, происходит из случаев выше: JPEG, цвет с профилем или плашечный, табулированный или mesh-градиент, либо графика, нарисованная внутри form XObject, чьи инструкции живут в собственном потоке, а не в содержимом страницы
Если вы также строите векторную графику, а не только преобразуете её, разбор векторной графики, шейдеров и градиентов показывает, как шейдинги и паттерны создаются в первую очередь, что делает градиентные правила выше легче для предсказания
Цветовой проход бежит в процессе: PDF Library for Delphi — нативный Pascal для Delphi, C++Builder и Lazarus, так что нормализация пакета входящих файлов не требует на сервере внешнего конвертера или инструмента командной строки. Страница продукта PDF Library for Delphi перечисляет остальной API обработки документов