Technical Article

Tilføjelse af JPEG 2000-billeder til PDF'er i Delphi med HotPDF

Et scannet medicinsk objektglas, en luftfotografisk kortflade, en filmramme arkiveret med fuldt dynamisk omfang. Dette er de billeder, der ankommer som JPEG 2000, og de ankommer på den måde af en grund. Formatet bevarer 12 eller 16 bit per kanal, komprimerer med en wavelet-transformation i stedet for den block-DCT, som JPEG bruger, og kan kode det samme billede enten tabsfrit eller tabsgivende fra én codestream. Når et dokument, der er bygget fra disse kilder, skal blive til en PDF, skal billedet rejse gennem et filter, som PDF-specifikationen reserverer til præcis denne codec

HotPDF v2.228.0 gendannede en fungerende JPEG 2000-dekodningsmaskine til den sti. Et tidligere build havde leveret enheden med stubfunktioner, der returnerede nil, så API'en eksisterede, men dekodede ingenting. Den aktuelle motor binder OpenJPEG 2.5.4 statisk og oversætter en JP2- eller J2K-kilde til pixels, som HotPDF kan placere på en side

JPXDecode-filteret i PDF

ISO 32000-1 definerer JPXDecode-filteret i §7.4.9. Et PDF-billede XObject angiver sin komprimering i stream-ordbogen under /Filter-posten, og JPXDecode er værdien, der angiver, at streamdataene er en JPEG 2000-codestream frem for den baseline-JPEG, som /DCTDecode bærer. Filteret er det, der lader en PDF indeholde wavelet-komprimerede billeddata med høj bitdybde, og det tillader både den tabsfrie og den tabsgivende tilstand af codeccen, da tilstanden er en egenskab ved codestreamen selv og ikke wrapperen around den

Det sidste punkt er det, der er værd at huske. JPEG 2000 er én enkelt algoritme med et tabsfrit specialtilfælde, ikke to separate formater. Den reversible 5/3-wavelet rekonstruerer de originale samplinger nøjagtigt; den irreversible 9/7-wavelet handler den præcision for en mindre fil. En decoder behandler begge ens ved læsetidspunktet, hvilket er grunden til, at HotPDF kun behøver én dekodningssti for at acceptere, hvad en JPXDecode-stream end sender

Hvad decoderen gør ved pixels

PDF-billede XObjects forventer i det almindelige tilfælde 8 bit per komponent i DeviceGray eller DeviceRGB. JPEG 2000 overskrider rutinemessigt det, og dens komponentmodel er mere generel end et pakket raster, så decoderen har tre opgaver, inden dataene kan bruges som et normalt billede

For det første resamples høj-bitdybde-komponenter til 8 bit. En 12-bit eller 16-bit sample skaleres ned til området 0 til 255, så resultatet er et almindeligt 8-bit-raster. Fortegnede komponenter skiftes til ikke-fortegnede områder først. Detaljen er vigtig, fordi den i sig selv er tabsgivende: en 16-bit gråtonescanning mister sin dybe tonale række, når den bliver et 8-bit PDF-billede, hvilket er den rette afvejning til skærm- og printoutput, men ikke til genarkivering

For det andet konverteres et YCbCr-farverum (codeccen kalder det SYCC) til RGB. JPEG 2000 gemmer ofte farve i et luma-chroma-rum for komprimeringseffektivitet, den samme idé baseline JPEG bruger, og decoderen anvender den standard inverse transformation, så siden modtager ægte RGB

For det tredje opsamples subsamplede komponenter ved nærmeste-nabo-replikation. Chroma-kanaler gemmes ofte ved halvt opløsning, så decoderen læser hver komponent med sine egne dimensioner og sin egen samplingsfrekvens og replikerer derefter samplinger for at bringe hver kanal op på den fulde billedstørrelse før sammenfletning. Nærmeste nabo holder trinnet billigt; den chroma, den udfylder, var lavfrekvent til at begynde med, så den synlige pris er lille

JP2-bokse kontra en rå J2K-codestream

En JPEG 2000-fil kommer i to former, og HotPDF registrerer, hvilken den læser, fra de første bytes frem for fra filudvidelsen. En JP2-fil er en boksstruktureret container: den åbner med den tolvbytes signaturbloks 00 00 00 0C 6A 50 20 20 og pakker codestreamen sammen med bokse, der beskriver farverum, opløsning og metadata. En rå J2K-codestream bærer ingen container overhovedet og begynder med SOC-markeren FF 4F FF 51. Decoderen læser disse indledende bytes, genkender signaturen og vælger den matchende OpenJPEG-codec for hvert tilfælde

Begge former håndteres, fordi begge forekommer i virkeligheden. Optagelsesenheder og arkiver, der har brug for sidemetadata, udsender JP2; værktøjer, der vil have den mindste mulige nyttelast, udsender den bare codestream. Formattypen er modelleret som en optælling, TJpeg2000FileType, med medlemmerne jtInvalid, jtJP2, jtJ2K og jtJPT. JPT-medlemmet navngiver JPIP-streamingvarianten; byte-signaturdetektoren opløser de to former, den kan dekode, JP2 og J2K, og rapporterer alt andet som jtInvalid, så et ikke-understøttet input fejler rent i stedet for at producere fejldata

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;

Tabsfri og tabsgivende på encodersiden

Decoderen læser begge tilstande uden at få besked om, hvilken det er. Valget er først en parameter, når du går den anden vej og producerer en JPEG 2000-fil, hvilket HotPDF også kan via klassen TJpeg2000Bitmap, en TBitmap-efterkommer, der indlæser og gemmer rasterdata som JP2. To egenskaber styrer outputtet. LosslessCompression er en boolean, der vælger den reversible wavelet, når den er sand; CompressionQuality er en TJpeg2000QualityRange, et heltal fra 1 til 100, hvor 1 er lille og grim og 100 er stor og tro. Standardværdierne befinder sig i navngivne konstanter: Jpeg2000DefaultLosslessCompression er False og Jpeg2000DefaultLossyQuality er 80

Beslutningen er en indholdsbeslutning. Tabsfri passer til en masterkopi, en medicinsk eller juridisk scanning, alt, der måske genencodes senere og ikke må akkumulere generationstab. Tabsgivende ved kvalitet 80 passer til et billede, der er på vej til skærm eller print, hvor waveletens nydèlse i kvalitetstab giver en mærkbart mindre fil uden artefakter, som en læser ville opdage. Der er et CMYK-forbehold at nævne: bitmapfilen eksponerer SetCMYK til at markere fire-kanals data som CMYK frem for RGBA, hvilket er vigtigt for print-pipelines, der bevarer separationer intakte

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;

Hvorfor der ikke er nogen decode-ved-indlæsning-filterpipeline

Et arkitektonisk faktum former, hvordan du bruger alt dette, og det er let at antage det modsatte. HotPDF har ingen generel decode-ved-indlæsning-billedfilter. Når du åbner en PDF, der allerede indeholder et JPXDecode-billede, dekoder motoren ikke den stream. Den bevarer JPEG 2000-bytes, nøjagtig som de er, så en sidekopi eller et dokumentsammenfletning transporterer billedet uberort igennem, byte for byte. Decoderen har eet enkelt indgangspunkt, og det er på oprettelsessiden: den filbaserede AddImage, afsendt via filudvidelse for at håndtere .jp2-, .j2k-, .jpt- og .jpc-kilder

Den opdeling er det korrekte design frem for en begrænsning. At dekode en indlejret JPX-stream ved indlæsning, blot for at genencod den ved lagring, ville konvertere et tabsfrit arkiveret billede til et tabsgivende og oppuste ethvert sammenfletning, alt for et billede, du kun mente at flytte fra en PDF til en anden. At sende streamen igennem ordret er en tabsfri operation og en hurtig én. Dekodning udsættes til det eneste øjeblik, det virkelig er nødvendigt: når du giver motoren en JPEG 2000-fil fra disken og beder den om at rasterisere billedet til placering på en ny side. På det tidspunkt skal filen blive til pixels, og decoderen kører

Registrering af understøttelse og placering af et billede

JPEG 2000-billedregistrering er opt-in bag HPDF_REGISTER_JPEG2000_PICTURE-kompileringsswitch, som er slukket som standard. Årsagen er en reel konflikt og ikke blot forsigtighed: at registrere jp2-, j2k- og jpc-filformaterne globalt med TPicture kan interferere med BLOB-formatdetektionen, som ReportBuilders TppDBImage er afhængig af. Definér switchen, når den integration ikke er i spil, og filformaterne registreres, så TPicture genkender dem; lad være med at definere den, og AddImage-udvidelsesdispatch dekoder stadig JPEG 2000-filer direkte, fordi den sti ikke går igennem TPicture overhovedet

Med det forstået er placering af et JPEG 2000-billede den samme tre-kald-rytme som ethvert andet HotPDF-billede. Giv AddImage en .jp2-sti og en komprimeringstype for, hvordan billedet skal lagres i outputtet, og positioner derefter det returnerede billedindeks på siden med 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;

Den komprimering, du sender til AddImage, styrer, hvordan det dekodede billede lagres igen, ikke hvordan det blev læst. En JPEG 2000-fil, der er dekoderet til et bitmap, kan gå tilbage som et DCTDecode JPEG, et Flate-raster eller et andet understøttet filter, alt efter hvad der passer til dokumentet. Dekodningen fra JP2 eller J2K sker først uanset hvad, så det samme kald accepterer en wavelet-komprimeret kilde og indlejrer den i den form, resten af din pipeline forventer

For et bredere billede af, hvordan billeder og skrifttyper lander i genereret output, se vores noter om rapportoutput med skrifttyper og billeder. Når det dokument, du samler, genbruger indhold fra eksisterende PDF'er, parres den gennemsendte adfærd, der beskrives her, med sammenfletnings- og revisionsmekanikken i objekt-streams og trinvise opdateringer. JPEG 2000-dekoderingsmotoren leveres som del af HotPDF Component til Delphi og C++Builder, ved siden af billed-, skrifttype- og dokument-API'erne, der er dækket andetsteds på denne blog