PDF Library for Delphi создаёт оба вида мягких масок PDF из Delphi. SetImageSoftMaskEx присоединяет изображение-маску к целевому изображению и может записать запись /Matte, необходимую для графики с предварительным умножением. NewSoftMaskGraphicsState строит маску альфа-канала или яркости из захваченного содержимого и возвращает состояние графики, которое вы применяете к последующей отрисовке. А когда потребитель вообще не умеет работать с прозрачностью, FlattenTransparency отрисовывает визуальный результат и заменяет им каждую страницу
Мягкие маски — это то, как PDF выражает градиентное затухание, виньетку, фото, вплетённое в фон, и любой другой эффект, где непрозрачность меняется по области. Постоянная прозрачность — это одно число в состоянии графики; мягкая маска — целая функция от позиции, и пока вы не умеете её создавать, целая категория дизайна оказывается невоспроизводимой
Два типа масок и что каждый из них читает
Маска альфа-канала берёт свои значения из альфа-канала маскирующего содержимого. Маска яркости берёт их из яркости маскирующего содержимого, поэтому белая область полностью непрозрачна, а чёрная — полностью прозрачна. Именно к яркости тянутся дизайнеры, потому что рисование градиента в оттенках серого — естественный способ описать затухание
Мягкие маски-формы строятся из захваченного содержимого, и несколько деталей зафиксированы, а не опциональны. Захваченное содержимое становится изолированной группой прозрачности в DeviceRGB, поэтому маска вычисляется в известном цветовом пространстве. Поэтому цвет фона обязан иметь три компонента, соответствуя этой группе. Функция передачи принимает /Identity или валидный объект функции, позволяя переформировать кривую маски без перерисовки графики
uses
PDFlibrary;
var
Lib: TPDFlib;
MaskCapture, MaskGS, PhotoID: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('brochure.pdf', '');
// Страница 4 несёт градиент в оттенках серого, нарисованный для использования как маска
MaskCapture := Lib.CapturePage(4);
MaskGS := Lib.NewSoftMaskGraphicsState(MaskCapture,
PDF_SOFT_MASK_LUMINOSITY, '0 0 0', '/Identity');
Lib.SelectPage(1);
Lib.SetGraphicsState(MaskGS);
PhotoID := Lib.AddImageFromFile('hero.jpg', 0);
Lib.SelectImage(PhotoID);
Lib.DrawImage(40, 700, 515, 260); // затухает под маской
// Настройки ExtGState сохраняются: явно завершаем область действия
Lib.SetGraphicsState(Lib.NewSoftMaskGraphicsState(0,
PDF_SOFT_MASK_NONE, '', ''));
Lib.SaveToFile('brochure-masked.pdf');
finally
Lib.Free;
end;
end;
Этот последний шаг — не украшение. Параметры состояния графики сохраняются, пока их не изменят, и применение обычного состояния графики впоследствии не очищает активную мягкую маску. Без явного /SMask /None всё, что нарисовано после замаскированной графики, наследует маску — это один из самых сбивающих с толку багов для визуальной диагностики
Маски изображений и запись Matte
Для маскирования на уровне изображения изображение-маска сначала нормализуется в DeviceGray, поэтому маска, поданная в любом цветовом пространстве, ведёт себя предсказуемо. Опциональная запись /Matte, от одного до четырёх компонентов в зависимости от цветового пространства изображения, сообщает потребителю, что данные изображения предварительно умножены на этот цвет, что предотвращает тёмный ореол, появляющийся, когда графика с предварительным умножением компонуется так, будто это прямая альфа
Здесь важны два ограждения. PDF/A-1 и PDF/X-1 запрещают прозрачность, и вызов отклоняется до изменения хоть одного объекта, а не создаёт файл, который позже провалит проверку. А успешная мягкая маска требует PDF 1.4, что библиотека устанавливает автоматически — прозрачности не существовало до этой версии, и заголовок 1.3 на файле, содержащем мягкие маски, попросту лжив
Повторяющиеся состояния переиспользуются: одна и та же захваченная форма, тип маски, фон и сигнатура функции передачи дают один и тот же объект состояния графики, а не новый на каждый вызов. На документе, маскирующем сотню изображений одинаково, это девяносто девять объектов, которые никогда не записываются
Зачем сводить и что сведение делает на самом деле
Прозрачность по-прежнему самый частый источник ситуации «на экране выглядит правильно, а на печати неправильно». Старые RIP, некоторые системы регуляторной подачи и весь PDF/X-1a требуют файл вообще без живой прозрачности
Сведение здесь намеренно буквально: каждая страница в запрошенном диапазоне отрисовывается при выбранном DPI на непрозрачном фоне, и содержимое и ресурсы страницы заменяются этим единственным изображением. Поскольку рендерер выдаёт итоговый скомпонованный результат, это покрывает всё сразу — группы прозрачности, режимы наложения, мягкие маски, узоры и вложенные формы — без логики приближения для каждой конструкции по отдельности, где можно было бы ошибиться. Пиковый расход памяти на растр остаётся ограниченным одной страницей
// Сводим страницы 1-8 при 300 DPI на белом фоне, затем сохраняем как PDF 1.3
Lib.FlattenTransparency('1-8', 300, $FFFFFF, 0);
Lib.SaveToFile('press-ready.pdf');
Расплата столь же буквальна: вывод — это изображение, поэтому текст больше не выделяется и не ищется, а векторные края передискретизируются при выбранном DPI. Сведение — это шаг доставки, а не шаг редактирования. Держите прозрачный оригинал и сводите копию
Повёрнутые страницы, пользовательские единицы и рамки страницы
Повёрнутая страница требует внимания, которое наивная реализация пропускает. После отрисовки визуальные ширина и высота записываются обратно в MediaBox, пользовательская единица сворачивается в эффективный DPI и новый размер страницы, а запись /Rotate, пользовательская единица и вспомогательные рамки страницы удаляются
Пропуск этого шага даёт файл, где сведённое изображение корректно, но программа просмотра поворачивает его ещё раз или масштабирует по пользовательской единице, которая больше не применима. Удаление рамок вместо их пересчёта — правильное решение, потому что TrimBox, описывающий геометрию до сведения, хуже, чем полное отсутствие TrimBox
Аннотации и поля формы по умолчанию отрисовываются в статичное изображение, а интерактивные объекты удаляются — именно это нужно для готового к печати результата. Если вместо этого вы сохраняете интерактивные объекты, проход аннотаций рендерера отключается, чтобы ничего не рисовалось дважды
Проверка версии, которая сканирует всё
Сохранение как PDF 1.3 требует, чтобы прозрачности нигде не осталось, и предсохраняющая проверка изучает каждый косвенный объект, а не только те, что достижимы со страниц. Это строже, чем кажется на первый взгляд, и это правильно: осиротевшая мягкая маска, оставленная более ранней правкой, всё ещё в файле, и потребитель, обходящий объекты, а не страницы, её найдёт
Чтобы понижение версии было достижимым, по умолчанию после успешной замены каждой целевой страницы выполняется проход пометки и сборки мусора, освобождающий недостижимые объекты. Следствие, которое стоит учесть при проектировании: идентификаторы изображений, захваченных страниц и состояний графики, полученные до этой сборки, могут после неё оказаться недействительными. Заново получите нужные идентификаторы ресурсов, прежде чем продолжить редактирование, либо отключите сборку мусора и смиритесь с тем, что версию тогда понизить не удастся
Два спутника дополняют картину. Векторная графика, использующая заливки и градиенты вместо масок, описана в статье векторная графика, шейдеры и градиенты, и это часто лучший путь, когда эффект можно выразить как заливку. А если причина сведения — требование к цвету, а не к прозрачности, путь преобразования в статье перекраска документа в другое цветовое пространство сохраняет живой текст там, где сведение бы его не сохранило
Создание мягких масок, сведение и стоящий за ними движок рендеринга поставляются в одной библиотеке для Delphi, C++Builder и Free Pascal; полный список возможностей — на странице PDF Library for Delphi