Відсканований медичний знімок, фрагмент аерофотозйомки, кадр плівки, заархівований із повним динамічним діапазоном. Це зображення, які надходять у форматі JPEG 2000, і вони надходять у такому вигляді не просто так. Формат зберігає 12 або 16 бітів на канал, стискає за допомогою вейвлет-перетворення замість блокового DCT, який використовує JPEG, і може кодувати одне й те саме зображення як без втрат, так і з втратами з одного кодового потоку. Коли документ, створений із цих джерел, має стати PDF-файлом, зображення має пройти через фільтр, який специфікація PDF резервує саме для цього кодека
HotPDF v2.228.0 відновив робочий рушій декодування JPEG 2000 для цього шляху. Попередня збірка постачала модуль із функціями-заглушками, які повертали nil, тому API існував, але нічого не декодував. Поточний рушій статично зв'язує OpenJPEG 2.5.4 і перетворює джерело JP2 або J2K у пікселі, які HotPDF може розмістити на сторінці
Фільтр JPXDecode у PDF
ISO 32000-1 визначає фільтр JPXDecode у §7.4.9. Зображення XObject у PDF вказує своє стиснення у записі /Filter словника потоку, і JPXDecode - це значення, яке говорить, що дані потоку є кодовим потоком JPEG 2000, а не базовим JPEG, який несе /DCTDecode. Цей фільтр - це те, що дозволяє PDF містити дані зображення, стиснені вейвлетом, із високою глибиною кольору, і він допускає як режими кодека без втрат, так і з втратами, оскільки режим є властивістю самого кодового потоку, а не його обгортки
Цей останній момент - це те, за що варто триматися. JPEG 2000 - це єдиний алгоритм з особливим випадком без втрат, а не два окремі формати. Оборотний вейвлет 5/3 точно реконструює оригінальні зразки; необоротний вейвлет 9/7 розмінює цю точність на менший розмір файлу. Декодер обробляє їх однаково під час читання, і саме тому HotPDF потребує лише одного шляху декодування, щоб прийняти все, що надсилає потік JPXDecode
Що декодер робить з пікселями
Зображення XObjects у PDF у загальному випадку очікують 8 бітів на компонент у DeviceGray або DeviceRGB. JPEG 2000 зазвичай перевищує це значення, і його модель компонентів є більш загальною, ніж запакований растр, тому декодер має виконати три завдання, перш ніж дані можна буде використовувати як звичайне зображення
По-перше, компоненти з високою глибиною кольору передискретизуються до 8 бітів. 12-бітовий або 16-бітовий зразок масштабується до діапазону від 0 до 255, щоб результат був звичайним 8-бітовим растром. Знакові компоненти спочатку зсуваються в беззнаковий діапазон. Ця деталь має значення, оскільки вона сама по собі несе втрати: 16-бітове сканування у відтінках сірого втрачає свій глибокий тональний діапазон у той момент, коли воно стає 8-бітовим зображенням PDF, що є правильним компромісом для виведення на екран та друк, але не для повторного архівування
По-друге, колірний простір YCbCr (кодек називає його SYCC) перетворюється на RGB. JPEG 2000 часто зберігає колір у просторі яскравості-колірності (luma-chroma) для ефективності стиснення, ту саму ідею використовує базовий JPEG, і декодер застосовує стандартне зворотне перетворення, щоб сторінка отримала справжній RGB
По-третє, субдискретизовані компоненти (subsampled) підвищуються шляхом реплікації найближчого сусіда. Канали колірності часто зберігаються з половинною роздільною здатністю, тому декодер зчитує кожен компонент за його власними розмірами та власним коефіцієнтом дискретизації, а потім реплікує зразки, щоб довести кожен канал до повного розміру зображення перед чергуванням. Метод найближчого сусіда (nearest-neighbor) зберігає цей крок дешевим; колірність, яку він заповнює, від початку була низькочастотною, тому видима втрата якості є невеликою
Блоки JP2 проти чистого кодового потоку J2K
Файл JPEG 2000 має дві форми, і HotPDF визначає, яку саме він читає, за першими байтами, а не за розширенням файлу. Файл JP2 - це контейнер блокової структури: він відкривається дванадцятибайтовим блоком підпису 00 00 00 0C 6A 50 20 20 і містить кодовий потік разом із блоками, що описують колірний простір, роздільну здатність і метадані. Чистий (raw) кодовий потік J2K взагалі не містить контейнера і починається з маркера SOC FF 4F FF 51. Декодер зчитує ці початкові байти, розпізнає підпис і вибирає відповідний кодек OpenJPEG для кожного випадку
Обидві форми обробляються, оскільки обидві зустрічаються на практиці. Пристрої захоплення та архіви, яким потрібні додаткові метадані, генерують JP2; інструменти, яким потрібне мінімально можливе корисне навантаження, генерують чистий кодовий потік. Тип формату моделюється як enum TJpeg2000FileType, з елементами jtInvalid, jtJP2, jtJ2K та jtJPT. Елемент JPT позначає варіант потокового передавання JPIP; детектор байтових підписів розрізняє дві форми, які він може декодувати, JP2 і J2K, і повідомляє про все інше як jtInvalid, щоб непідтримуваний вхідний файл завершувався коректно, а не виробляв сміття
uses
HPDFJpeg2000;
var
Decoder: THPDFJpeg2000Decoder;
Pixels: TJpeg2000ByteArray;
begin
Decoder := THPDFJpeg2000Decoder.Create;
try
if Decoder.LoadFromStream(Input) then // JP2 or J2K, auto-detected
if Decoder.GetImageData(Pixels) then
// Pixels is 8-bit interleaved, ColorComponents channels wide,
// row-major top to bottom: ready for a DeviceGray/DeviceRGB XObject.
ProcessRaster(Decoder.Width, Decoder.Height,
Decoder.ColorComponents, Pixels);
finally
Decoder.Free;
end;
end;
Без втрат і з втратами на стороні кодування
Декодер читає обидва режими, і йому не потрібно повідомляти, який це режим. Вибір стає параметром лише тоді, коли ви йдете у зворотному напрямку і створюєте файл JPEG 2000, що HotPDF також може робити за допомогою класу TJpeg2000Bitmap - нащадка TBitmap, який завантажує і зберігає растрові дані як JP2. Вихідними даними керують дві властивості. LosslessCompression - це логічне значення, яке вибирає оборотний вейвлет, коли воно дорівнює true; CompressionQuality - це TJpeg2000QualityRange, ціле число від 1 до 100, де 1 - це маленький розмір і низька якість, а 100 - це великий розмір і висока точність. Типові значення знаходяться в іменованих константах: Jpeg2000DefaultLosslessCompression дорівнює False, а Jpeg2000DefaultLossyQuality - 80
Це рішення залежить від контенту. Без втрат (lossless) підходить для майстер-копії, медичного або юридичного скану, всього, що згодом може бути перекодоване і не повинно накопичувати втрати від багатьох генерацій. З втратами (lossy) при якості 80 підходить для зображення, призначеного для екрана або друку, де поступова деградація вейвлета дає помітно менший файл без артефактів, які б міг помітити читач. Є один нюанс щодо CMYK, на який варто звернути увагу: растрове зображення пропонує функцію SetCMYK, щоб позначити чотириканальні дані як CMYK, а не RGBA, що важливо для робочих процесів друку, які зберігають кольороподіл недоторканим
uses
HPDFJpeg2000;
var
Bmp: TJpeg2000Bitmap;
begin
Bmp := TJpeg2000Bitmap.Create;
try
Bmp.LoadFromStream(Source); // decode an existing JP2/J2K
Bmp.LosslessCompression := True; // reversible 5/3 wavelet
// or, for a smaller lossy file:
// Bmp.LosslessCompression := False;
// Bmp.CompressionQuality := 80; // matches the default
Bmp.SaveToStream(Output); // always writes a JP2 file
finally
Bmp.Free;
end;
end;
Чому немає конвеєра фільтрів декодування при завантаженні
Один архітектурний факт визначає те, як ви все це використовуєте, і легко припустити протилежне. У HotPDF немає загального фільтра зображень для декодування при завантаженні (decode-on-load). Коли ви відкриваєте PDF, який уже містить зображення JPXDecode, рушій не декодує цей потік. Він зберігає байти JPEG 2000 саме такими, якими вони є, тому копіювання сторінки або об'єднання документів переносить зображення недоторканим, байт за байтом. Декодер має єдину точку входу, і вона знаходиться на стороні створення: функція AddImage, що працює з файлами, і диспетчеризується за розширенням файлу для обробки джерел .jp2, .j2k, .jpt та .jpc
Такий поділ є правильним архітектурним рішенням, а не обмеженням. Декодування вбудованого потоку JPX під час завантаження лише для того, щоб перекодувати його під час збереження, перетворило б заархівоване зображення без втрат на зображення з втратами та роздуло б кожне злиття, і все це заради картинки, яку ви просто хотіли перемістити з одного PDF до іншого. Проходження потоку без змін - це операція без втрат, до того ж швидка. Декодування відкладається до того єдиного моменту, коли воно дійсно потрібне: коли ви передаєте рушію файл JPEG 2000 з диска і просите його раструвати це зображення для розміщення на новій сторінці. У цей момент файл має стати пікселями, і тоді запускається декодер
Реєстрація підтримки та розміщення зображення
Реєстрація зображень JPEG 2000 включається за бажанням за допомогою перемикача компіляції HPDF_REGISTER_JPEG2000_PICTURE, який за замовчуванням вимкнено. Причиною є реальний конфлікт, а не обережність: глобальна реєстрація форматів файлів jp2, j2k і jpc у TPicture може заважати виявленню формату BLOB, на яке покладається TppDBImage у ReportBuilder. Визначте цей перемикач, коли ця інтеграція не використовується, і формати файлів зареєструються, щоб TPicture їх розпізнавав; залиште його невизначеним, і диспетчеризація розширень AddImage все одно декодуватиме файли JPEG 2000 безпосередньо, оскільки цей шлях взагалі не проходить через TPicture
Зважаючи на це, розміщення зображення JPEG 2000 - це той самий ритм із трьох викликів, що й для будь-якого іншого зображення HotPDF. Передайте AddImage шлях до файлу .jp2 і тип стиснення для того, як зображення має зберігатися на виході, а потім розташуйте повернутий індекс зображення на сторінці за допомогою ShowImage
var
Pdf: THotPDF;
ImgIndex: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.AddPage;
// The .jp2 source is decoded through the OpenJPEG backend, then
// re-embedded with the compression you request here.
ImgIndex := Pdf.AddImage('Scan_16bit.jp2', icJpeg);
// x, y, width, height in points; final 0 is the rotation angle.
Pdf.ShowImage(ImgIndex, 72, 72, 400, 300, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Стиснення, яке ви передаєте до AddImage, керує тим, як декодоване зображення буде повторно збережено, а не тим, як його було прочитано. Файл JPEG 2000, декодований у растрове зображення, може вийти як JPEG DCTDecode, Flate-растр або інший підтримуваний фільтр - що більше підходить для документа. Декодування з JP2 або J2K відбувається в першу чергу в будь-якому випадку, тому той самий виклик приймає джерело, стиснене вейвлетом, і вбудовує його в тій формі, якої очікує решта вашого конвеєра
Щоб отримати ширшу картину того, як зображення та шрифти потрапляють у згенерований вивід, дивіться наші нотатки про виведення звітів зі шрифтами та зображеннями. Коли документ, який ви збираєте, повторно використовує вміст з існуючих PDF-файлів, описана тут наскрізна (passthrough) поведінка поєднується з механізмами злиття та перегляду в потоках об'єктів та поступових оновленнях. Рушій декодування JPEG 2000 постачається як частина HotPDF Component для Delphi та C++Builder, поруч з API для зображень, шрифтів та документів, що розглядаються в інших публікаціях цього блогу