Technical Article

JPEG 2000 vaizdų pridėjimas į PDF failus Delphi aplinkoje su HotPDF

Nuskaityta medicininė skaidrė, aerofotografijos plytelė, filmo kadras, suarchyvuotas visu dinaminiu diapazonu. Tai yra vaizdai, kurie gaunami JPEG 2000 formatu, ir tam yra priežastis. Formatas išlaiko 12 arba 16 bitų kanale, suspaudžia naudodamas bangelių (wavelet) transformaciją vietoje blokų DCT, kurią naudoja JPEG, ir gali užkoduoti tą patį vaizdą be nuostolių arba su nuostoliais iš vieno kodo srauto. Kai dokumentas, sukurtas iš tokių šaltinių, turi tapti PDF, vaizdas turi pereiti per filtrą, kurį PDF specifikacija rezervuoja būtent šiam kodekui

HotPDF v2.228.0 atstatė veikiantį JPEG 2000 dekodavimo variklį šiam keliui. Ankstesnė versija pateikė modulį su tuščiomis funkcijomis, kurios grąžindavo nil, todėl API egzistavo, bet nieko nedekodavo. Dabartinis variklis statiškai susieja OpenJPEG 2.5.4 ir paverčia JP2 arba J2K šaltinį pikseliais, kuriuos HotPDF gali patalpinti puslapyje

JPXDecode filtras PDF failuose

ISO 32000-1 apibrėžia JPXDecode filtrą §7.4.9 skyriuje. PDF vaizdo XObject nurodo savo kompresiją srauto žodyno /Filter įraše, ir JPXDecode yra reikšmė, kuri sako, kad srauto duomenys yra JPEG 2000 kodo srautas, o ne bazinis JPEG, kurį neša /DCTDecode. Būtent filtras leidžia PDF failui talpinti bangelių kompresija suspaustus didelio bitų gylio vaizdo duomenis, ir jis priima tiek kodeko režimą be nuostolių, tiek su nuostoliais, nes režimas yra paties kodo srauto savybė, o ne jį gaubiančio apvalkalo

Būtent šį paskutinį punktą verta įsidėmėti. JPEG 2000 yra vienas algoritmas, turintis specialų atvejį be nuostolių, o ne du atskiri formatai. Grįžtamoji 5/3 bangelė (wavelet) tiksliai atkuria originalius pavyzdžius; negrįžtamoji 9/7 bangelė aukoja tą tikslumą dėl mažesnio failo dydžio. Dekoduotojas skaitymo metu abu atvejus traktuoja vienodai, todėl HotPDF pakanka vieno dekodavimo kelio, kad priimtų viską, ką jam pateikia JPXDecode srautas

Ką dekoduotojas daro su pikseliais

PDF vaizdų XObject elementai įprastai tikisi 8 bitų vienam komponentui DeviceGray arba DeviceRGB spalvų erdvėse. JPEG 2000 paprastai tai viršija, ir jo komponentų modelis yra bendresnis nei supakuotas rastras, todėl dekoduotojas turi atlikti tris darbus, kol duomenis galima naudoti kaip įprastą vaizdą

Pirma, didelio bitų gylio komponentai perskaičiuojami į 8 bitus. 12 arba 16 bitų pavyzdys sumažinamas iki 0-255 diapazono, kad rezultatas būtų paprastas 8 bitų rastras. Komponentai su ženklu pirmiausia perkeliami į diapazoną be ženklo. Ši detalė svarbi, nes ji savaime praranda duomenis: 16 bitų pilkų atspalvių nuskaitymas praranda gilų tonų diapazoną tą akimirką, kai tampa 8 bitų PDF vaizdu. Tai teisingas kompromisas išvesčiai į ekraną ir spaudai, bet ne pakartotiniam archyvavimui

Antra, YCbCr (kodekas tai vadina SYCC) spalvų erdvė konvertuojama į RGB. Dėl suspaudimo efektyvumo JPEG 2000 dažnai saugo spalvas šviesio ir chromiškumo (luma-chroma) erdvėje – tą pačią idėją naudoja ir bazinis JPEG, o dekoduotojas pritaiko standartinę atvirkštinę transformaciją, kad puslapis gautų tikrą RGB

Trečia, nepilnos imties (subsampled) komponentų raiška padidinama naudojant artimiausio kaimyno dubliavimą. Chromiškumo (chroma) kanalai dažnai saugomi perpus mažesne raiška, todėl dekoduotojas nuskaito kiekvieną komponentą pagal jo matmenis ir diskretizavimo dažnį, o tada padaugina pavyzdžius, kad prieš persipinant kiekvienas kanalas atitiktų pilną vaizdo dydį. Artimiausio kaimyno metodas užtikrina, kad šis žingsnis būtų pigus skaičiavimo atžvilgiu; spalvų informacija, kurią jis užpildo, nuo pat pradžių buvo žemo dažnio, todėl matomi nuostoliai yra nedideli

JP2 dėžutės palyginti su grynu J2K kodo srautu

JPEG 2000 failas gali būti dviejų formų, ir HotPDF iš pirmųjų baitų, o ne iš failo plėtinio nustato, kurį iš jų jis skaito. JP2 failas yra dėžučių struktūros konteineris: jis prasideda dvylikos baitų parašo dėžute 00 00 00 0C 6A 50 20 20 ir apgaubia kodo srautą kartu su dėžutėmis, aprašančiomis spalvų erdvę, raišką ir metaduomenis. Grynas J2K kodo srautas neturi jokio konteinerio ir prasideda SOC žymekliu FF 4F FF 51. Dekoduotojas nuskaito tuos pradinius baitus, atpažįsta parašą ir kiekvienam atvejui parenka atitinkamą OpenJPEG kodeką

Abi formos yra apdorojamos, nes abi pasitaiko praktikoje. Fiksavimo įrenginiai ir archyvai, kuriems reikia papildomų metaduomenų, išveda JP2; įrankiai, kuriems reikia kuo mažesnio failo dydžio, išveda gryną kodo srautą. Formato tipas modeliuojamas kaip išvardijimas (enum) TJpeg2000FileType su nariais jtInvalid, jtJP2, jtJ2K ir jtJPT. Narys JPT nurodo JPIP srautinio perdavimo variantą; baitų parašų detektorius išsprendžia dvi formas, kurias jis gali dekoduoti, JP2 ir J2K, ir praneša apie visa kita kaip apie jtInvalid, todėl nepalaikoma įvestis aiškiai nepavyksta, o ne sukuria šiukšles

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;

Be nuostolių ir su nuostoliais kodavimo pusėje

Dekoduotojas nuskaito abu režimus be išankstinio nurodymo. Šis pasirinkimas tampa parametru tik tada, kai einate į kitą pusę ir kuriate JPEG 2000 failą, ką HotPDF taip pat gali padaryti per TJpeg2000Bitmap klasę, t. y. TBitmap palikuonį, kuris įkelia ir išsaugo rastro duomenis kaip JP2. Išvestį valdo dvi savybės. LosslessCompression yra loginė reikšmė (boolean), kuri, kai yra teisinga (true), pasirenka grįžtamąją bangelę; CompressionQuality yra TJpeg2000QualityRange – sveikasis skaičius nuo 1 iki 100, kur 1 reiškia mažą ir prastos kokybės, o 100 – didelį ir tikslų. Numatytosios reikšmės saugomos pavadintose konstantose: Jpeg2000DefaultLosslessCompression yra False, o Jpeg2000DefaultLossyQuality yra 80

Šis sprendimas priklauso nuo turinio. Išsaugojimas be nuostolių tinka pagrindinei kopijai, medicininiam ar teisiniam nuskaitymui – bet kam, kas vėliau gali būti perkoduojama ir neturi kaupti kartų praradimo (generational loss). Išsaugojimas su nuostoliais naudojant 80 kokybę tinka paveikslėliui, skirtam ekranui ar spaudai, kur nuoseklus bangelių kokybės mažėjimas suteikia pastebimai mažesnį failą be jokių artefaktų, kuriuos pastebėtų skaitytojas. Yra viena CMYK išlyga, kurią reikia pažymėti: rastrinis vaizdas atskleidžia SetCMYK metodą, kad keturių kanalų duomenys būtų pažymėti kaip CMYK, o ne RGBA, kas svarbu spausdinimo procesams, išlaikantiems spalvų atskyrimą

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;

Kodėl nėra dekodavimo įkeliant filtrų konvejerio

Vienas architektūrinis faktas formuoja tai, kaip visa tai naudosite, ir nesunku daryti priešingą prielaidą. HotPDF neturi bendro vaizdų dekodavimo įkeliant filtro. Kai atidarote PDF failą, kuriame jau yra JPXDecode vaizdas, variklis nedekoduoja to srauto. Jis išlaiko JPEG 2000 baitus tiksliai tokius, kokie jie yra, todėl kopijuojant puslapį ar sujungiant dokumentus vaizdas perkeliamas nepaliestas – baitas į baitą. Dekoduotojas turi tik vieną įėjimo tašką, ir jis yra kūrimo pusėje: failais paremtas AddImage, nukreipiamas pagal failo plėtinį, kad apdorotų .jp2, .j2k, .jpt ir .jpc šaltinius

Toks atskyrimas yra teisingas dizainas, o ne apribojimas. Įterpto JPX srauto dekodavimas jį įkeliant, kad išsaugant jis vėl būtų perkoduotas, paverstų be nuostolių suarchyvuotą vaizdą į vaizdą su nuostoliais ir išpūstų kiekvieną sujungimą vien dėl paveikslėlio, kurį tiesiog norėjote perkelti iš vieno PDF į kitą. Srauto perdavimas pažodžiui yra operacija be nuostolių ir atliekama greitai. Dekodavimas atidedamas iki to vienintelio momento, kai jo iš tikrųjų prireikia: kai varikliui pateikiate JPEG 2000 failą iš disko ir paprašote jį rasterizuoti, kad patalpintumėte naujame puslapyje. Tuo momentu failas turi tapti pikseliais, ir dekoduotojas pradedamas vykdyti

Palaikymo registravimas ir vaizdo patalpinimas

JPEG 2000 vaizdų registravimas yra pasirenkamas naudojant HPDF_REGISTER_JPEG2000_PICTURE kompiliavimo jungiklį, kuris pagal nutylėjimą yra išjungtas. To priežastis yra realus konfliktas, o ne atsargumas: jp2, j2k ir jpc failų formatų globalus registravimas TPicture klasėje gali trukdyti BLOB formatų aptikimui, kuriuo remiasi ReportBuilder komponentas TppDBImage. Apibrėžkite jungiklį, kai ši integracija nenaudojama, ir failų formatai bus užregistruoti taip, kad TPicture juos atpažintų; palikite jį neapibrėžtą, ir AddImage plėtinių dispečeris vis tiek tiesiogiai dekoduos JPEG 2000 failus, nes šis kelias visiškai neina per TPicture

Tai supratus, JPEG 2000 vaizdo patalpinimas atliekamas tuo pačiu trijų iškvietimų ritmu, kaip ir bet kurio kito HotPDF vaizdo atveju. Perduokite AddImage funkcijai .jp2 kelią ir kompresijos tipą, nurodantį, kaip vaizdas turėtų būti išsaugotas išvestyje, tada gautą vaizdo indeksą patalpinkite puslapyje naudodami 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;

Kompresija, kurią perduodate į AddImage, valdo tai, kaip dekoduotas paveikslėlis išsaugomas iš naujo, o ne kaip jis buvo nuskaitytas. JPEG 2000 failas, dekoduotas į rastrinį vaizdą, gali būti vėl išvestas kaip DCTDecode JPEG, Flate rastras ar kitas palaikomas filtras – priklausomai nuo to, kas tinka dokumentui. Dekodavimas iš JP2 arba J2K bet kokiu atveju įvyksta pirmiausia, todėl tas pats iškvietimas priima bangelių kompresija suspaustą šaltinį ir įterpia jį bet kokia forma, kurios tikisi likęs jūsų konvejeris

Jei norite susidaryti platesnį vaizdą, kaip vaizdai ir šriftai patenka į sugeneruotą išvestį, skaitykite mūsų užrašus apie ataskaitų išvestį su šriftais ir vaizdais. Kai jūsų kuriamame dokumente pakartotinai naudojamas turinys iš esamų PDF failų, čia aprašytas tiesioginio perdavimo elgesys dera su sujungimo ir revizijų mechanika straipsnyje objektų srautai ir inkrementiniai atnaujinimai. JPEG 2000 dekodavimo variklis pateikiamas kaip HotPDF Component, skirto Delphi ir C++Builder, dalis, kartu su vaizdų, šriftų ir dokumentų API, aptartais kitur šiame tinklaraštyje