Artykuł techniczny

Dodawanie obrazów JPEG 2000 do plików PDF w Delphi za pomocą HotPDF

Skan histologiczny, kafel zdjęcia lotniczego, klatka filmowa zarchiwizowana z pełnym zakresem dynamicznym. To obrazy, które docierają jako JPEG 2000, i docierają w tym formacie z konkretnego powodu. Format zachowuje 12 lub 16 bitów na kanał, kompresuje transformatą falkową zamiast blokowego DCT stosowanego w JPEG i potrafi zakodować ten sam obraz bezstratnie lub stratnie z jednego codestream. Gdy dokument zbudowany z takich źródeł musi stać się plikiem PDF, obraz musi przejść przez filtr, który specyfikacja PDF rezerwuje wyłącznie dla tego kodeka

HotPDF v2.228.0 przywrócił działający silnik dekodowania JPEG 2000 dla tej ścieżki. Wcześniejsza wersja zawierała jednostkę z funkcjami zastępczymi zwracającymi nil, więc API istniało, ale niczego nie dekodowało. Obecny silnik wiąże statycznie OpenJPEG 2.5.4 i zamienia źródło JP2 lub J2K w piksele, które HotPDF może umieścić na stronie

Filtr JPXDecode w PDF

Norma ISO 32000-1 definiuje filtr JPXDecode w §7.4.9. Obraz XObject w PDF podaje swój algorytm kompresji we wpisie /Filter słownika strumienia, a JPXDecode to wartość oznaczająca, że dane strumienia stanowią codestream JPEG 2000, a nie standardowy JPEG przenoszony przez /DCTDecode. Filtr pozwala plikowi PDF przechowywać dane obrazu skompresowane falkowo z wysoką głębią bitową i obsługuje zarówno bezstratny, jak i stratny tryb kodeka, ponieważ tryb ten jest właściwością samego codestreamu, a nie otoczki wokół niego

Ta ostatnia kwestia jest warta zapamiętania. JPEG 2000 to jeden algorytm ze specjalnym przypadkiem bezstratnym, a nie dwa odrębne formaty. Odwracalna transformata falkowa 5/3 rekonstruuje oryginalne próbki dokładnie; nieodwracalna transformata 9/7 wymienia tę dokładność na mniejszy plik. Dekoder traktuje oba przypadki tak samo podczas odczytu, dlatego HotPDF potrzebuje tylko jednej ścieżki dekodowania, aby obsłużyć cokolwiek dostarczy strumień JPXDecode

Co dekoder robi z pikselami

Obrazy XObject PDF w typowym przypadku oczekują 8 bitów na komponent w DeviceGray lub DeviceRGB. JPEG 2000 rutynowo przekracza ten zakres, a jego model komponentów jest bardziej ogólny niż spakowany raster, więc dekoder ma trzy zadania do wykonania, zanim dane nadadzą się do użycia jako normalny obraz

Po pierwsze, komponenty o wysokiej głębi bitowej są przepróbkowywane do 8 bitów. Próbka 12-bitowa lub 16-bitowa jest skalowana w dół do zakresu 0-255, aby wynik był zwykłym rastrem 8-bitowym. Komponenty ze znakiem są najpierw przesuwane do zakresu bez znaku. Ten szczegół ma znaczenie, bo samo przepróbkowanie jest stratne: 16-bitowy skan w skali szarości traci swój bogaty zakres tonalny w chwili, gdy staje się 8-bitowym obrazem PDF, co jest właściwym kompromisem dla wyświetlania i druku, ale nie do ponownej archiwizacji

Po drugie, przestrzeń barw YCbCr (w kodeku nazywana SYCC) jest konwertowana do RGB. JPEG 2000 często przechowuje kolor w przestrzeni luminancja-chrominancja ze względu na wydajność kompresji, podobnie jak robi to standardowy JPEG, a dekoder stosuje standardową odwrotną transformatę, aby strona otrzymała prawdziwe RGB

Po trzecie, podpróbkowane komponenty są próbkowane w górę przez replikację najbliższego sąsiada. Kanały chrominancji są często przechowywane w połowie rozdzielczości, więc dekoder odczytuje każdy komponent w jego własnych wymiarach i własnym współczynniku próbkowania, a następnie replikuje próbki, aby wyrównać każdy kanał do pełnego rozmiaru obrazu przed przeplotem. Najbliższy sąsiad utrzymuje tę operację tanią; chrominancja, którą wypełnia, była od początku niskoczęstotliwościowa, więc koszt wizualny jest niewielki

Kontenery JP2 kontra surowy codestream J2K

Plik JPEG 2000 ma dwie postaci, a HotPDF wykrywa, którą czyta, na podstawie pierwszych bajtów, a nie rozszerzenia pliku. Plik JP2 to kontener o strukturze pudełkowej: otwiera się dwunastobajtowym pudełkiem sygnatury 00 00 00 0C 6A 50 20 20 i owija codestream wraz z pudełkami opisującymi przestrzeń barw, rozdzielczość i metadane. Surowy codestream J2K nie ma żadnego kontenera i zaczyna się od znacznika SOC FF 4F FF 51. Dekoder odczytuje te wiodące bajty, rozpoznaje sygnaturę i wybiera odpowiedni kodek OpenJPEG dla każdego przypadku

Obie postaci są obsługiwane, bo obie spotyka się w praktyce. Urządzenia rejestrujące i archiwa potrzebujące metadanych bocznych emitują JP2; narzędzia wymagające jak najmniejszego ładunku emitują surowy codestream. Typ formatu jest modelowany jako wyliczenie TJpeg2000FileType z elementami jtInvalid, jtJP2, jtJ2K i jtJPT. Element JPT oznacza wariant strumieniowania JPIP; detektor sygnatury bajtowej rozwiązuje dwie postaci, które potrafi zdekodować - JP2 i J2K - a wszystko inne zgłasza jako jtInvalid, żeby nieobsługiwane wejście kończyło się czysto zamiast produkować śmieci

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;

Kompresja bezstratna i stratna po stronie kodowania

Dekoder odczytuje oba tryby bez informacji, który z nich to jest. Wybór staje się parametrem dopiero gdy idziesz w drugą stronę i produkujesz plik JPEG 2000, co HotPDF potrafi również przez klasę TJpeg2000Bitmap - descendanta TBitmap ładującego i zapisującego dane rastrowe jako JP2. Dwie właściwości kontrolują dane wyjściowe. LosslessCompression to wartość logiczna wybierająca odwracalną transformatę falkową, gdy jest ustawiona na True; CompressionQuality to TJpeg2000QualityRange, liczba całkowita od 1 do 100, gdzie 1 oznacza mały i brzydki, a 100 duży i wierny. Wartości domyślne żyją w nazwanych stałych: Jpeg2000DefaultLosslessCompression to False, a Jpeg2000DefaultLossyQuality to 80

Decyzja jest decyzją merytoryczną. Bezstratna kompresja pasuje do kopii wzorcowej, skanu medycznego lub prawnego, czegokolwiek, co może być ponownie kodowane później i nie może akumulować strat generacyjnych. Stratna przy jakości 80 pasuje do obrazu przeznaczonego na ekran lub do druku, gdzie łagodna degradacja transformaty falkowej daje wyraźnie mniejszy plik bez artefaktów, które czytelnik by zauważył. Jedna uwaga dotycząca CMYK: bitmapa udostępnia SetCMYK, aby oznaczyć dane czterokanałowe jako CMYK zamiast RGBA, co ma znaczenie dla potoków druku zachowujących separacje

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;

Dlaczego nie ma potoku filtrującego podczas ładowania

Jeden fakt architektoniczny kształtuje sposób korzystania z całego tego mechanizmu i łatwo założyć odwrotność. HotPDF nie ma ogólnego filtru dekodującego obrazy podczas ładowania. Gdy otwierasz plik PDF, który zawiera już obraz JPXDecode, silnik nie dekoduje tego strumienia. Zachowuje bajty JPEG 2000 dokładnie tak, jak są, więc kopiowanie strony lub scalanie dokumentów przenosi obraz bez żadnych zmian, bajt po bajcie. Dekoder ma jeden punkt wejścia i jest po stronie tworzenia: oparty na pliku AddImage, wysyłany według rozszerzenia pliku do obsługi źródeł .jp2, .j2k, .jpt i .jpc

Ten podział to właściwy projekt, a nie ograniczenie. Dekodowanie osadzonego strumienia JPX podczas ładowania tylko po to, żeby go ponownie zakodować przy zapisie, zamieniłoby bezstratny zarchiwizowany obraz w stratny i powiększyłoby każde scalanie - wszystko dla obrazu, który chciałeś po prostu przenieść z jednego PDF do drugiego. Przepuszczanie strumienia bez zmian to operacja bezstratna i szybka. Dekodowanie jest odraczane do jedynego momentu, gdy jest naprawdę konieczne: gdy podajesz silnikowi plik JPEG 2000 z dysku i prosisz go o rasteryzację tego obrazu do umieszczenia na nowej stronie. W tym momencie plik musi stać się pikselami i dekoder jest uruchamiany

Rejestrowanie obsługi i umieszczanie obrazu

Rejestracja obsługi obrazów JPEG 2000 jest opcjonalna za przełącznikiem kompilacji HPDF_REGISTER_JPEG2000_PICTURE, który jest domyślnie wyłączony. Przyczyną jest rzeczywisty konflikt, a nie ostrożność: globalna rejestracja formatów plików jp2, j2k i jpc w TPicture może interferować z detekcją formatu BLOB, na której polega TppDBImage z ReportBuildera. Zdefiniuj przełącznik, gdy ta integracja nie jest używana, a formaty plików zostaną zarejestrowane, żeby TPicture je rozpoznawał; zostaw go niezdefiniowanego, a wysyłanie rozszerzeń przez AddImage nadal będzie dekodować pliki JPEG 2000 bezpośrednio, bo ta ścieżka w ogóle nie przechodzi przez TPicture

Mając to na uwadze, umieszczenie obrazu JPEG 2000 przebiega tak samo jak każdego innego obrazu HotPDF - trzy wywołania w tej samej kolejności. Podaj AddImage ścieżkę .jp2 i typ kompresji, w jakiej obraz ma być przechowywany w pliku wyjściowym, a następnie umieść zwrócony indeks obrazu na stronie za pomocą 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;

Kompresja przekazywana do AddImage kontroluje sposób ponownego przechowywania zdekodowanego obrazu, a nie sposób jego odczytu. Plik JPEG 2000 zdekodowany do bitmapy może być ponownie zapisany jako DCTDecode JPEG, raster Flate lub inny obsługiwany filtr - cokolwiek odpowiada dokumentowi. Dekodowanie z JP2 lub J2K następuje najpierw niezależnie od tego, więc to samo wywołanie akceptuje źródło skompresowane falkowo i osadza je w dowolnej formie, jakiej oczekuje reszta potoku

Szerszy obraz tego, jak obrazy i czcionki trafiają do generowanych dokumentów, znajdziesz w naszych notatkach o generowaniu raportów z czcionkami i obrazami. Gdy składany dokument ponownie wykorzystuje treść z istniejących plików PDF, opisane tutaj zachowanie przepuszczania strumienia łączy się z mechaniką scalania i rewizji w artykule o strumieniach obiektów i aktualizacjach przyrostowych. Silnik dekodowania JPEG 2000 jest dostarczany jako część HotPDF Component dla Delphi i C++Builder, wraz z interfejsami API obrazów, czcionek i dokumentów omówionymi w innych częściach tego bloga