Technical Article

Lägga till JPEG 2000-bilder i PDF med HotPDF i Delphi

Ett skannat medicinskt preparatglas, en bildruta från en flygfotografering, en filmruta arkiverad med fullt dynamiskt omfång. Dessa är bilderna som anländer som JPEG 2000, och de anländer så av en anledning. Formatet behåller 12 eller 16 bitar per kanal, komprimerar med en wavelet-transform i stället för det block-DCT som JPEG använder, och kan koda samma bild antingen förlustfritt eller förstörande från en enda kodström. När ett dokument byggt från dessa källor ska bli en PDF måste bilden passera genom ett filter som PDF-specifikationen reserverar för exakt denna codec

HotPDF v2.228.0 återställde en fungerande JPEG 2000-avkodningsmotor för den vägen. En tidigare version hade levererat enheten med stubbfunktioner som returnerade nil, så API:et fanns men avkodade ingenting. Den nuvarande motorn binder OpenJPEG 2.5.4 statiskt och omvandlar en JP2- eller J2K-källa till pixlar som HotPDF kan placera på en sida

JPXDecode-filtret i PDF

ISO 32000-1 definierar filtret JPXDecode i §7.4.9. Ett PDF-bild-XObject namnger sin komprimering i strömordbokens /Filter-post, och JPXDecode är det värde som säger att strömdata är en JPEG 2000-kodström och inte den baslinje-JPEG som /DCTDecode bär på. Filtret är vad som låter en PDF rymma wavelet-komprimerad bilddata med högt bitdjup, och det tillåter både det förlustfria och det förstörande läget av codec:en, eftersom läget är en egenskap hos kodströmmen själv och inte hos omslaget runt den

Den sista punkten är den som är värd att hålla fast vid. JPEG 2000 är en enda algoritm med ett förlustfritt specialfall, inte två separata format. Den reversibla 5/3-wavelet:en rekonstruerar de ursprungliga samplingarna exakt; den irreversibla 9/7-wavelet:en byter ut den exaktheten mot en mindre fil. En avkodare behandlar båda på samma sätt vid läsning, vilket är anledningen till att HotPDF bara behöver en avkodningsväg för att acceptera vad en JPXDecode-ström än kastar på den

Vad avkodaren gör med pixlarna

I det vanliga fallet förväntar sig PDF-bild-XObjects 8 bitar per komponent i DeviceGray eller DeviceRGB. JPEG 2000 överskrider rutinmässigt detta, och dess komponentmodell är mer generell än ett packat raster, så avkodaren har tre jobb att utföra innan datan är användbar som en normal bild

För det första samplas komponenter med högt bitdjup om till 8 bitar. Ett 12-bitars eller 16-bitars prov skalas ner till intervallet 0 till 255 så att resultatet blir ett vanligt 8-bitars raster. Signerade komponenter skiftas till osignerat intervall först. Detaljen spelar roll eftersom den i sig själv innebär förlust: en 16-bitars gråskaleskanning förlorar sitt djupa tonomfång i samma ögonblick som den blir en 8-bitars PDF-bild, vilket är rätt avvägning för skärm- och utskriftsutmatning men inte för återarkivering

För det andra konverteras en YCbCr-färgrymd (codec:en kallar det SYCC) till RGB. JPEG 2000 lagrar ofta färg i en luma-kroma-rymd för komprimeringseffektivitet, samma idé som baslinje-JPEG använder, och avkodaren tillämpar den vanliga inversa transformen så att sidan får äkta RGB

För det tredje uppsamplas nedsamplade komponenter genom närmaste-granne-replikering. Kromakanaler lagras ofta i halverad upplösning, så avkodaren läser varje komponent med dess egna dimensioner och dess egen samplingsfaktor, och replikerar sedan prover för att föra upp varje kanal till bildens fulla storlek innan sammanflätning sker. Närmaste-granne håller steget billigt; kromat som den fyller var lågfrekvent från början, så den synliga kostnaden är liten

JP2-lådor kontra en rå J2K-kodström

En JPEG 2000-fil kommer i två former, och HotPDF upptäcker vilken den läser utifrån de första byten snarare än från filändelsen. En JP2-fil är en lådstrukturerad behållare: den inleds med den tolv byte långa signaturlådan 00 00 00 0C 6A 50 20 20 och omsluter kodströmmen tillsammans med lådor som beskriver färgrymd, upplösning och metadata. En rå J2K-kodström bär inte på någon behållare alls och börjar med SOC-markören FF 4F FF 51. Avkodaren läser dessa inledande byten, känner igen signaturen och väljer matchande OpenJPEG-codec för varje fall

Båda formerna hanteras eftersom båda förekommer ute i naturen. Fångstenheter och arkiv som behöver sidometadatan avger JP2; verktyg som vill ha den minsta möjliga nyttolasten avger den rena kodströmmen. Formattypen modelleras som en enum, TJpeg2000FileType, med medlemmarna jtInvalid, jtJP2, jtJ2K och jtJPT. JPT-medlemmen namnger streamingvarianten JPIP; den bytesignatur-baserade detektorn löser de två former den kan avkoda, JP2 och J2K, och rapporterar allt annat som jtInvalid så att en inmatning som inte stöds misslyckas rent i stället för att producera skräp

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örlustfritt och förstörande på kodningssidan

Avkodaren läser båda lägena utan att få veta vilket det är. Valet blir en parameter först när du går åt andra hållet och producerar en JPEG 2000-fil, vilket HotPDF också kan göra via TJpeg2000Bitmap-klassen, en TBitmap-ättling som laddar och sparar rasterdata som JP2. Två egenskaper styr utmatningen. LosslessCompression är ett booleskt värde som väljer den reversibla wavelet:en när den är sann; CompressionQuality är ett TJpeg2000QualityRange, ett heltal från 1 till 100 där 1 är litet och fult och 100 är stort och naturtroget. Standardvärdena lever i namngivna konstanter: Jpeg2000DefaultLosslessCompression är False och Jpeg2000DefaultLossyQuality är 80

Beslutet är ett innehållsbeslut. Förlustfritt passar en masterkopia, en medicinsk eller juridisk skanning, allt som kan kodas om senare och inte får ackumulera generationsförluster. Förstörande på kvalitet 80 passar en bild som är på väg mot skärm eller utskrift, där wavelet:ens graciösa nedbrytning ger en märkbart mindre fil utan någon artefakt en läsare skulle upptäcka. Det finns en CMYK-varning att flagga för: bitmappen exponerar SetCMYK för att markera fyrkanalsdata som CMYK i stället för RGBA, vilket spelar roll för utskriftsflöden som behåller separationer intakta

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;

Varför det inte finns någon avkoda-vid-inläsning-filterpipeline

Ett arkitektoniskt faktum formar hur du använder något av detta, och det är lätt att anta motsatsen. HotPDF har inget generellt bildfilter för avkodning vid inläsning. När du öppnar en PDF som redan innehåller en JPXDecode-bild, avkodar motorn inte den strömmen. Den behåller JPEG 2000-byten exakt som de är, så en sidkopiering eller en dokumentsammanfogning för med sig bilden orörd, byte för byte. Avkodaren har en enda ingångspunkt, och den ligger på skapandesidan: den filbaserade AddImage, som utifrån filändelse skickar vidare för att hantera .jp2, .j2k, .jpt och .jpc-källor

Denna uppdelning är en korrekt design i stället för en begränsning. Att avkoda en inbäddad JPX-ström vid inläsning, bara för att koda om den när den sparas, skulle konvertera en förlustfritt arkiverad bild till en förstörande sådan och blåsa upp varje sammanfogning, allt för en bild du bara menade att flytta från en PDF till en annan. Att låta strömmen passera bokstavligen är en förlustfri operation och en snabb sådan. Avkodning skjuts upp till det enda ögonblick det verkligen krävs: när du ger motorn en JPEG 2000-fil från disken och ber den att rastrera den bilden för placering på en ny sida. Då måste filen bli till pixlar, och avkodaren körs

Registrera stöd och placera en bild

Registrering av JPEG 2000-bilder måste väljas till aktivt bakom kommandoväxeln HPDF_REGISTER_JPEG2000_PICTURE, som är avstängd som standard. Anledningen är en verklig konflikt, inte försiktighet: att registrera filformaten jp2, j2k och jpc globalt med TPicture kan störa den detektering av BLOB-format som ReportBuilders TppDBImage förlitar sig på. Definiera växeln när den integrationen inte är aktuell, och filformaten registreras så att TPicture känner igen dem; lämna den odefinierad och filändelsedispatchen för AddImage avkodar ändå JPEG 2000-filer direkt, eftersom den vägen inte går genom TPicture alls

Med detta förstått är placeringen av en JPEG 2000-bild samma treanropsrytm som vilken annan HotPDF-bild som helst. Ge AddImage en .jp2-sökväg och en komprimeringstyp för hur bilden ska lagras i utmatningen, och placera sedan det returnerade bildindexet på sidan 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 skickar till AddImage styr hur den avkodade bilden lagras om, inte hur den lästes. En JPEG 2000-fil avkodad till en bitmapp kan gå ut igen som en DCTDecode-JPEG, ett Flate-raster eller ett annat filter som stöds, beroende på vad som passar dokumentet. Avkodningen från JP2 eller J2K sker först oavsett, så samma anrop accepterar en wavelet-komprimerad källa och bäddar in den i vilken form som helst som resten av din pipeline förväntar sig

För en bredare bild av hur bilder och teckensnitt landar i den genererade utmatningen, se våra anteckningar om rapportutmatning med teckensnitt och bilder. När det dokument du bygger återanvänder innehåll från befintliga PDF:er samspelar det pass-through-beteende som beskrivs här med sammanfognings- och revisionsmekaniken i objektströmmar och inkrementella uppdateringar. JPEG 2000-avkodningsmotorn levereras som en del av HotPDF Component för Delphi och C++Builder, vid sidan av de bild-, teckensnitts- och dokument-API:er som täcks på andra ställen på denna blogg