Отсканированный договор - это несколько сотен точек на дюйм чёрных чернил на белой бумаге. В виде однобитового растрового изображения он уже небольшой, однако сотня таких страниц всё равно раздуёт PDF до объёма, непригодного для отправки по электронной почте. Правильный фильтр меняет арифметику. JBIG2 - это сжатие с наибольшим коэффициентом, которое ISO 32000-1 определяет для двухуровневых изображений: на стопке сканированного текста оно регулярно вдвое превосходит CCITT Group 4. Именно этот фильтр нужно использовать, когда входные данные получены по факсу, отсканированы или иным образом сведены к двум цветам, и HotPDF может записать его непосредственно в PDF
Формат достигает такого коэффициента благодаря двум идеям, которых нет у обобщённых кодеков. Он моделирует расположение чёрных фрагментов на белом фоне, а также замечает, что отсканированная страница в основном состоит из нескольких сотен форм глифов, повторяющихся тысячи раз. Понимание обоих принципов позволяет выбирать параметры кодирования осознанно, а не наугад
Место JBIG2 в спецификации PDF
ISO 32000-1 перечисляет JBIG2Decode среди фильтров потоков в §7.4.7, доступных начиная с PDF 1.4. Он применяется только в одном месте: для XObject-изображений, у которых /BitsPerComponent равно 1, а цветовое пространство разрешается в один канал. В этом весь смысл. JBIG2 - двухуровневый кодек, поэтому он никогда не конкурирует с DCT или JPXDecode на фотографиях. Он конкурирует с CCITTFaxDecode, фильтрами факса Group 3 и Group 4, именно на двухтональных страницах, которые производит документальный сканер
Декодер потребляет встроенную организацию JBIG2, которую стандарт называет PDF-профилем: каждый поток изображения содержит последовательность сегментов, а не простой битовый поток. Необязательный поток /JBIG2Globals несёт сегменты, общие для нескольких изображений в одном документе - это механизм, позволяющий хранить повторяющееся содержимое один раз для всего файла, а не для каждой страницы отдельно. HotPDF по умолчанию формирует поток на изображение и оставляет канал globals свободным, если только бэкенд не запрашивает его
Архитектура кодировщика с бэкендом
Полноценный кодировщик JBIG2 - это крупный программный компонент, и наиболее эффективные его части исторически были обременены патентами и поставлялись под лицензиями, не подходящими для каждого продукта. HotPDF решает это противоречие, разделяя интерфейс и движок. Модуль HPDFJBIG2 определяет вызовы, используемые остальной частью библиотеки, и поставляет скромный встроенный кодировщик, чтобы JBIG2 работал «из коробки». Когда нужны коэффициенты сжатия производственного уровня, регистрируется более мощный движок, и библиотека делегирует ему без каких-либо изменений в вызывающем коде
Переключение - это единственный вызов регистрации. Без зарегистрированного бэкенда кодировщик переходит на встроенный путь; зарегистрируйте его - и все последующие кодирования будут проходить через него
uses
HPDFJBIG2;
// Запрос того, что активно, с последующей (опциональной) установкой более мощного движка.
if not IsJBIG2EncoderBackendAvailable then
// Производственный бэкенд не присутствует: HotPDF использует свой встроенный путь MMR.
RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);
// Позже, чтобы вернуться к встроенному поведению:
// ClearJBIG2Backends;
Аналогичный хук для декодирования существует через RegisterJBIG2DecoderBackend, а IsJBIG2DecoderBackendAvailable позволяет его проверить. Именно поэтому библиотека поставляется со встроенным небольшим путём плюс стыковочным швом для бэкенда, а не с монолитным кодировщиком. Встроенный путь сохраняет бинарник компактным и свободным от лицензионных ограничений, тогда как шов позволяет команде, лицензировавшей полный кодировщик, подключить его без каких-либо изменений на уровне записи PDF
Что в действительности меняют параметры кодирования
Кодирование настраивается через TJBIG2EncodeOptions - запись с полями Lossless, UseGlobalSegments, UseSymbolDictionary и LossyLevel. Дружественная к компонентам обёртка THPDFJBIG2Options публикует Lossless, UseSymbolDictionary и LossyLevel, чтобы их можно было задавать из Инспектора объектов, и внутренне преобразует их в запись. Настройками управляют три намерения
Режим без потерь сохраняет каждый пиксель. Установите Lossless в True и оставьте LossyLevel равным нулю - декодированный растр будет побитово идентичен входному. Это единственный безопасный выбор для штриховой графики, технических чертежей и любой страницы, где пропуск пикселя может изменить смысл - например, подписи или штампа. Кодирование со словарём символов включает дедупликацию с учётом текста и является параметром, отличающим JBIG2 от факсимильных фильтров. Уровень потерь - целое число от 0 до 9 - позволяет способному бэкенду жертвовать точностью ради размера, трактуя почти идентичные знаки как один символ. Ноль означает без потерь. Встроенный кодировщик обрабатывает только режим без потерь и игнорирует любой ненулевой уровень потерь, поэтому более высокие уровни вступают в силу только после регистрации их реализующего бэкенда
var
Options: TJBIG2EncodeOptions;
begin
Options := DefaultJBIG2EncodeOptions; // Lossless True, symbol dictionary включен
Options.Lossless := True;
Options.LossyLevel := 0; // 0 сохраняет каждый пиксель
Options.UseSymbolDictionary := True; // дедупликация повторяющихся глифов
// Передайте Options в бэкенд или позвольте THPDFJBIG2Options хранить их.
end;
Словари символов и почему текстовые сканы выигрывают
Страница сканированного текста - это вовсе не изображение слов. Это одна и та же буква е, напечатанная несколько сотен раз, та же т, та же запятая, каждый экземпляр - немного зашумлённая копия одной исходной формы. Словарь символов улавливает эту структуру. Кодировщик собирает отдельные знаки со страницы в словарь, сохраняет каждую форму один раз, а затем записывает страницу как список позиций, ссылающихся на записи словаря. Тысяча вхождений одного и того же глифа обходится в одно сохранённое изображение плюс тысяча дешёвых позиций размещения
Именно здесь JBIG2 опережает CCITT Group 4. Group 4 кодирует каждую строку сканирования относительно строки выше без какого-либо понятия о глифе, поэтому платит полную стоимость за каждую букву при каждом её появлении. JBIG2 платит один раз. Когда один и тот же словарь выносится в поток globals на уровне документа, экономия накапливается по всему многостраничному сканированию, поскольку формы, общие для страницы за страницей, хранятся единожды для всего файла. На плотном тексте разница не является незначительной - именно для этого и создавался JBIG2
Обобщённые регионы и MMR для всего остального
Не каждое двухуровневое изображение является текстом. Карты, схемы, инженерные чертежи и смешанные страницы содержат штриховую графику, которую никакой словарь не может обобщить. Для них JBIG2 кодирует обобщённый регион - прямоугольник пикселей, сжатый напрямую без какого-либо обучения символам. Стандарт разрешает обобщённому региону использовать MMR - кодирование «modified modified READ», применяемое факсом Group 4, которое моделирует каждую строку пикселей относительно строки выше
Именно этот путь HotPDF использует во встроенном кодировщике. Когда бэкенд не зарегистрирован и запрос лишён потерь, библиотека сжимает растр как единый обобщённый регион MMR и оборачивает его в структуру сегментов JBIG2, требуемую PDF-профилем. Словарь не нужен, обучающий проход не нужен, вторая ссылка на изображение не нужна - поэтому это надёжный вариант по умолчанию для штриховой графики и смешанного двухуровневого содержимого. На чистом тексте он не сравнится с полноценным кодировщиком словаря символов, однако всегда корректен, всегда без потерь и всегда присутствует. Поверхность кодировщика для этого случая - один вызов
var
Encoder: THPDFJBIG2Encoder;
ImageData: TJBIG2ByteArray;
Scanlines: TJBIG2ScanlineArray; // один массив байтов на строку, начиная со старшего бита (MSB-first)
W, H: Integer;
begin
// Scanlines, W и H описывают 1-битную страницу; каждая строка занимает (W + 7) div 8 байтов.
Encoder := THPDFJBIG2Encoder.Create;
try
if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
// Теперь ImageData содержит поток JBIG2, готовый для XObject с фильтром /JBIG2Decode.
;
finally
Encoder.Free;
end;
end;
Включение при создании документа
В обычном использовании вы не обращаетесь напрямую к классу кодировщика. HotPDF предоставляет JBIG2 как выбор сжатия изображения на уровне документа. Перечисление THPDFImageCompressionType включает icJBIG2 наряду с вариантами Flate, JPEG и CCITT, а документ несёт свойство JBIG2Options типа THPDFJBIG2Options, содержащее настройки, используемые при выборе этого сжатия. Настройте оба параметра перед добавлением двухуровневых изображений, которые нужно сжать таким образом
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.ImageCompressionType := icJBIG2; // пропускаем 1-битные изображения через JBIG2
Pdf.JBIG2Options.Lossless := True; // сохраняем каждый пиксель
Pdf.JBIG2Options.UseSymbolDictionary := True;
Pdf.JBIG2Options.LossyLevel := 0;
// Добавляйте страницы и размещайте здесь свои отсканированные 1-битные изображения.
finally
Pdf.Free;
end;
end;
Стоит отметить одно удобство: дополнение DBGridHotPDFExport отображает TDBGrid прямо в PDF. Его вывод в основном состоит из двухуровневых линий и текста, поэтому документ, настроенный для JBIG2, делает такие экспорты компактными без какой-либо дополнительной обработки с вашей стороны. Две связанные темы в блоге подробнее рассматривают окружающий рабочий процесс. О том, как изображения и шрифты расставляются при создании отчётов, рассказано в статье вывод отчётов с шрифтами и изображениями в Delphi. Когда сжатый документ должен соответствовать профилю архивного хранения, правила в статье валидация PDF/A, PDF/X и PDF/UA в Delphi указывают, какие фильтры принимает тот или иной уровень соответствия. JBIG2 входит в состав компонента HotPDF для Delphi и C++Builder, рядом с API загрузки, редактирования и шифрования, рассмотренными в других статьях