Articol Tehnic

Adăugarea imaginilor JPEG 2000 în PDF-uri Delphi cu HotPDF

O lamelă medicală scanată, o placă de sondaj aerian, un cadru de film arhivat la gama dinamică completă. Acestea sunt imaginile care sosesc ca JPEG 2000, și sosesc în acest fel dintr-un motiv. Formatul păstrează 12 sau 16 biți per canal, comprimă cu o transformată wavelet în loc de DCT bloc pe care îl folosește JPEG, și poate codifica aceeași imagine fie fără pierderi fie cu pierderi dintr-un singur codestream. Când un document construit din acele surse trebuie să devină un PDF, imaginea trebuie să treacă printr-un filtru pe care specificația PDF îl rezervă exact pentru acest codec

HotPDF v2.228.0 a restaurat un motor de decodare JPEG 2000 funcțional pentru acea cale. O versiune anterioară livrare unitatea cu funcții stub care returnau nil, deci API-ul exista dar nu decoda nimic. Motorul actual leagă static OpenJPEG 2.5.4 și transformă o sursă JP2 sau J2K în pixeli pe care HotPDF îi poate plasa pe o pagină

Filtrul JPXDecode în PDF

ISO 32000-1 definește filtrul JPXDecode în §7.4.9. Un XObject de imagine PDF își denumește compresia în intrarea /Filter a dicționarului de flux, iar JPXDecode este valoarea care spune că datele fluxului sunt un codestream JPEG 2000 mai degrabă decât JPEG-ul de bază pe care îl transportă /DCTDecode. Filtrul este ceea ce permite unui PDF să dețină date de imagine comprimate prin wavelet cu bit-depth ridicat, și admite atât modul fără pierderi cât și cel cu pierderi al codec-ului, deoarece modul este o proprietate a codestream-ului în sine și nu a învelișului din jurul său

Ultimul punct este cel care merită reținut. JPEG 2000 este un singur algoritm cu un caz special fără pierderi, nu două formate separate. Wavelet-ul reversibil 5/3 reconstituie eșantioanele originale exact; wavelet-ul ireversibil 9/7 schimbă acea exactitate pentru un fișier mai mic. Un decodificator le tratează pe ambele în același mod la citire, ceea ce este motivul pentru care HotPDF are nevoie de o singură cale de decodare pentru a accepta orice aruncă un flux JPXDecode

Ce face decodificatorul cu pixelii

XObject-urile de imagine PDF se așteaptă în mod obișnuit la 8 biți per componentă în DeviceGray sau DeviceRGB. JPEG 2000 depășește în mod obișnuit aceasta, iar modelul său de componente este mai general decât un raster compact, deci decodificatorul are trei sarcini de îndeplinit înainte ca datele să fie utilizabile ca imagine normală

În primul rând, componentele cu bit-depth ridicat sunt reeșantionate la 8 biți. Un eșantion de 12 sau 16 biți este scalat la intervalul 0 la 255 astfel încât rezultatul să fie un raster obișnuit de 8 biți. Componentele cu semn sunt mai întâi deplasate în intervalul fără semn. Detaliul contează deoarece este în sine cu pierderi: o scanare în tonuri de gri de 16 biți își pierde gama tonală profundă în momentul în care devine o imagine PDF de 8 biți, ceea ce este schimbul potrivit pentru ieșirea pe ecran și imprimare, dar nu pentru re-arhivare

În al doilea rând, un spațiu de culoare YCbCr (codec-ul îl numește SYCC) este convertit în RGB. JPEG 2000 stochează adesea culoarea într-un spațiu luma-chroma pentru eficiența compresiei, aceeași idee pe care o folosește JPEG-ul de bază, iar decodificatorul aplică transformata inversă standard astfel încât pagina să primească RGB adevărat

În al treilea rând, componentele subeșantionate sunt supraeșantionate prin replicare nearest-neighbor. Canalele chroma sunt frecvent stocate la jumătate de rezoluție, deci decodificatorul citește fiecare componentă la propriile dimensiuni și propriul factor de eșantionare, apoi replică eșantioanele pentru a aduce fiecare canal la dimensiunea completă a imaginii înainte de intercalare. Nearest-neighbor menține pasul ieftin; chroma pe care o completează era de frecvență joasă de la bun început, deci costul vizibil este mic

Cutiile JP2 versus un codestream J2K brut

Un fișier JPEG 2000 vine în două forme, și HotPDF detectează care este citind din primii octeți mai degrabă decât din extensia fișierului. Un fișier JP2 este un container structurat în cutii: se deschide cu cutia de semnătură de doisprezece octeți 00 00 00 0C 6A 50 20 20 și înfășoară codestream-ul alături de cutii care descriu spațiul de culoare, rezoluția și metadatele. Un codestream J2K brut nu poartă niciun container și începe cu marcatorul SOC FF 4F FF 51. Decodificatorul citește acei octeți de început, recunoaște semnătura și selectează codec-ul OpenJPEG corespunzător pentru fiecare caz

Ambele forme sunt gestionate deoarece ambele apar în realitate. Dispozitivele de capturare și arhivele care au nevoie de metadate laterale emit JP2; instrumentele care doresc cel mai mic payload posibil emit codestream-ul simplu. Tipul de format este modelat ca o enumerare, TJpeg2000FileType, cu membrii jtInvalid, jtJP2, jtJ2K, și jtJPT. Membrul JPT numește varianta de streaming JPIP; detectorul de semnătură de octeți rezolvă cele două forme pe care le poate decoda, JP2 și J2K, și raportează orice altceva ca jtInvalid astfel încât o intrare neacceptată eșuează curat în loc să producă date eronate

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;

Fără pierderi și cu pierderi pe partea de codare

Decodificatorul citește ambele moduri fără a i se spune care este. Alegerea devine un parametru numai când mergeți în celălalt sens și produceți un fișier JPEG 2000, ceea ce HotPDF poate face și prin clasa TJpeg2000Bitmap, un descendent TBitmap care încarcă și salvează date raster ca JP2. Două proprietăți guvernează ieșirea. LosslessCompression este un boolean care selectează wavelet-ul reversibil când este true; CompressionQuality este un TJpeg2000QualityRange, un întreg de la 1 la 100 unde 1 este mic și slab, iar 100 este mare și fidel. Valorile implicite trăiesc în constante numite: Jpeg2000DefaultLosslessCompression este False, iar Jpeg2000DefaultLossyQuality este 80

Decizia este o decizie de conținut. Fără pierderi se potrivește unei copii master, unei scanări medicale sau juridice, oricărui lucru care poate fi recodat ulterior și nu trebuie să acumuleze pierderi generaționale. Cu pierderi la calitatea 80 se potrivește unei imagini destinate ecranului sau imprimării, unde degradarea grațioasă a wavelet-ului oferă un fișier vizibil mai mic fără niciun artefact pe care un cititor l-ar observa. Există un avertisment CMYK de menționat: bitmap-ul expune SetCMYK pentru a marca datele cu patru canale ca CMYK mai degrabă decât RGBA, ceea ce contează pentru pipeline-urile de imprimare care mențin separările intacte

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;

De ce nu există un pipeline de filtre decode-on-load

Un fapt arhitectural modelează modul în care folosiți oricare dintre acestea, și este ușor să presupuneți contrariul. HotPDF nu are un filtru general de imagine decode-on-load. Când deschideți un PDF care conține deja o imagine JPXDecode, motorul nu decodifică acel flux. Păstrează octeții JPEG 2000 exact cum sunt, deci o copie de pagină sau o fuzionare de document poartă imaginea prin aceasta neatinsă, octet cu octet. Decodificatorul are un singur punct de intrare, și acesta se află pe partea de creare: AddImage bazat pe fișier, distribuit după extensia fișierului pentru a gestiona sursele .jp2, .j2k, .jpt, și .jpc

Acea separare este designul corect mai degrabă decât o limitare. Decodarea unui flux JPX încorporat la încărcare, numai pentru a-l re-codifica la salvare, ar converti o imagine arhivată fără pierderi într-una cu pierderi și ar umfla fiecare fuzionare, totul pentru o imagine pe care ați intenționat doar să o mutați dintr-un PDF în altul. Trecerea fluxului prin verbatim este o operație fără pierderi și rapidă. Decodarea este amânată la singurul moment în care este cu adevărat necesară: când înmânați motorului un fișier JPEG 2000 de pe disc și îi cereți să rasterizeze acea imagine pentru plasarea pe o pagină nouă. În acel moment fișierul trebuie să devină pixeli, și decodificatorul rulează

Înregistrarea suportului și plasarea unei imagini

Înregistrarea imaginilor JPEG 2000 este opt-in în spatele switch-ului de compilare HPDF_REGISTER_JPEG2000_PICTURE, care este dezactivat implicit. Motivul este un conflict real, nu precauție: înregistrarea formatelor de fișier jp2, j2k, și jpc global cu TPicture poate interfera cu detectarea formatului BLOB pe care TppDBImage al ReportBuilder se bazează. Definiți switch-ul când acea integrare nu este în joc, iar formatele de fișier se înregistrează astfel TPicture le recunoaște; lăsați-l nedefinit și distribuirea de extensie AddImage tot decodifică fișierele JPEG 2000 direct, deoarece acea cale nu trece prin TPicture deloc

Cu aceasta înțeles, plasarea unei imagini JPEG 2000 este același ritm de trei apeluri ca orice altă imagine HotPDF. Înmânați AddImage o cale .jp2 și un tip de compresie pentru cum ar trebui stocată imaginea în ieșire, apoi poziționați indexul de imagine returnat pe pagină cu 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;

Compresia pe care o transmiteți la AddImage controlează modul în care imaginea decodată este re-stocată, nu modul în care a fost citită. Un fișier JPEG 2000 decodat la bitmap poate ieși ca DCTDecode JPEG, un raster Flate, sau un alt filtru acceptat, oricare se potrivește documentului. Decodarea din JP2 sau J2K are loc mai întâi indiferent, deci același apel acceptă o sursă comprimată prin wavelet și o încorporează în oricare formă se așteaptă restul pipeline-ului dvs

Pentru imaginea mai largă a modului în care imaginile și fonturile ajung în ieșirea generată, consultați notele noastre despre ieșirea rapoartelor cu fonturi și imagini. Când documentul pe care îl asamblați reutilizează conținut din PDF-uri existente, comportamentul de trecere descris aici se îmbină cu mecanica de fuzionare și revizuire din fluxuri de obiecte și actualizări incrementale. Motorul de decodare JPEG 2000 este livrat ca parte din HotPDF Component pentru Delphi și C++Builder, alături de API-urile de imagine, font și document acoperite în altă parte pe acest blog