Een gescande medische dia, een luchtopnametegel, een filmframe gearchiveerd op volledig dynamisch bereik. Dit zijn de afbeeldingen die als JPEG 2000 aankomen, en daar is een reden voor. Het formaat behoudt 12 of 16 bits per kanaal, comprimeert met een wavelet-transformatie in plaats van de blok-DCT die JPEG gebruikt, en kan dezelfde afbeelding verliesvrij (lossless) of met verlies (lossy) coderen vanuit één codestream. Wanneer een document dat uit die bronnen is opgebouwd een PDF moet worden, moet de afbeelding door een filter reizen dat de PDF-specificatie precies voor deze codec reserveert
HotPDF v2.228.0 herstelde een werkende JPEG 2000-decodeerengine voor dat pad. Een eerdere build had de unit geleverd met stub-functies die nil retourneerden, dus de API bestond maar decodeerde niets. De huidige engine bindt OpenJPEG 2.5.4 statisch en zet een JP2- of J2K-bron om in pixels die HotPDF op een pagina kan plaatsen
Het JPXDecode-filter in PDF
ISO 32000-1 definieert het JPXDecode-filter in §7.4.9. Een PDF image XObject benoemt zijn compressie in de /Filter-vermelding van het streamwoordenboek, en JPXDecode is de waarde die zegt dat de streamdata een JPEG 2000-codestream is in plaats van de baseline JPEG die /DCTDecode met zich meedraagt. Het filter is wat een PDF in staat stelt wavelet-gecomprimeerde afbeeldingsdata met hoge bitdiepte te bevatten, en het laat zowel de lossless als de lossy modi van de codec toe, omdat de modus een eigenschap is van de codestream zelf en niet van de wrapper eromheen
Dat laatste punt is het vasthouden waard. JPEG 2000 is één enkel algoritme met een verliesvrij speciaal geval, niet twee afzonderlijke formaten. De omkeerbare 5/3-wavelet reconstrueert de originele samples exact; de onomkeerbare 9/7-wavelet ruilt die exactheid in voor een kleiner bestand. Een decoder behandelt beide op dezelfde manier op het moment van lezen, en daarom heeft HotPDF slechts één decodeerpad nodig om te accepteren wat een JPXDecode-stream er ook naar gooit
Wat de decoder met de pixels doet
PDF image XObjects verwachten in het algemeen 8 bits per component in DeviceGray of DeviceRGB. JPEG 2000 overschrijdt dat routinematig, en zijn componentmodel is algemener dan een ingepakt raster, dus de decoder heeft drie taken te doen voordat de data bruikbaar is als een normale afbeelding
Ten eerste worden componenten met een hoge bitdiepte geresampled naar 8 bits. Een 12-bit of 16-bit sample wordt teruggeschaald naar het bereik 0 tot 255, zodat het resultaat een gewoon 8-bit raster is. Geteekende ("signed") componenten worden eerst verschoven naar een ongetekend ("unsigned") bereik. Dit detail is van belang omdat het op zichzelf lossy is: een 16-bits grijswaardenscan verliest zijn diepe toonbereik op het moment dat het een 8-bits PDF-afbeelding wordt, wat de juiste ruil is voor scherm- en printuitvoer, maar niet voor herarchivering
Ten tweede wordt een YCbCr (de codec noemt het SYCC) kleurruimte geconverteerd naar RGB. JPEG 2000 slaat kleur vaak op in een luma-chroma ruimte voor compressie-efficiëntie, hetzelfde idee dat baseline JPEG gebruikt, en de decoder past de standaard inverse transformatie toe zodat de pagina ware RGB ontvangt
Ten derde worden gesubsampelde componenten geüpsampled door nearest-neighbor replicatie. Chromakanalen worden vaak opgeslagen met halve resolutie, dus de decoder leest elke component op zijn eigen afmetingen en zijn eigen bemonsteringsfactor ("sampling factor"), en repliceert vervolgens samples om elk kanaal naar de volledige afbeeldingsgrootte te brengen alvorens interleaving toe te passen. Nearest-neighbor houdt de stap goedkoop; de chroma die het vult was in eerste instantie laagfrequent, dus de zichtbare kosten zijn klein
JP2-boxen versus een ruwe J2K-codestream
Een JPEG 2000-bestand is er in twee vormen, en HotPDF detecteert welke het leest aan de hand van de eerste bytes in plaats van aan de hand van de bestandsextensie. Een JP2-bestand is een container met een boxstructuur: het opent met de twaalf-byte handtekeningbox 00 00 00 0C 6A 50 20 20 en wikkelt de codestream naast boxen die kleurruimte, resolutie en metadata beschrijven. Een ruwe J2K-codestream draagt helemaal geen container en begint met de SOC-markering FF 4F FF 51. De decoder leest die leidende bytes, herkent de handtekening en selecteert voor elk geval de bijbehorende OpenJPEG-codec
Beide vormen worden afgehandeld omdat beide in het wild voorkomen. Opnameapparatuur en archieven die de metadata ernaast nodig hebben zenden JP2 uit; tools die de kleinst mogelijke payload willen zenden de kale codestream uit. Het formaattype is gemodelleerd als een enum, TJpeg2000FileType, met de leden jtInvalid, jtJP2, jtJ2K en jtJPT. Het JPT-lid benoemt de JPIP streamingvariant; de byte-handtekeningdetector lost de twee vormen op die het kan decoderen, JP2 en J2K, en rapporteert al het andere als jtInvalid zodat een niet-ondersteunde invoer netjes faalt in plaats van rommel te produceren
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;
Lossless en lossy aan de codeerkant
De decoder leest beide modi zonder dat hem verteld wordt welke het is. De keuze wordt pas een parameter wanneer u de andere kant op gaat en een JPEG 2000-bestand produceert, wat HotPDF ook kan doen via de klasse TJpeg2000Bitmap, een TBitmap-afstammeling die rasterdata laadt en opslaat als JP2. Twee eigenschappen beheersen de uitvoer. LosslessCompression is een boolean die de omkeerbare wavelet selecteert indien waar; CompressionQuality is een TJpeg2000QualityRange, een geheel getal van 1 tot 100 waarbij 1 klein en lelijk is en 100 groot en getrouw. De standaardwaarden bevinden zich in benoemde constanten: Jpeg2000DefaultLosslessCompression is False en Jpeg2000DefaultLossyQuality is 80
De beslissing is een inhoudelijke beslissing. Lossless past bij een masterkopie, een medische of juridische scan, alles wat later opnieuw gecodeerd kan worden en geen generationeel verlies mag accumuleren. Lossy op kwaliteit 80 past bij een afbeelding bestemd voor scherm of print, waarbij de gracieuze degradatie van de wavelet een merkbaar kleiner bestand oplevert zonder enig artefact dat een lezer zou opvallen. Er is één CMYK-voorbehoud om te signaleren: de bitmap stelt SetCMYK beschikbaar om vier-kanaals data te markeren als CMYK in plaats van RGBA, wat van belang is voor printpipelines die scheidingen intact houden
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;
Waarom er geen decode-on-load filterpipeline is
Eén architecturaal feit vormt hoe u dit alles gebruikt, en het is makkelijk om het tegenovergestelde aan te nemen. HotPDF heeft geen algemeen decode-on-load afbeeldingsfilter. Wanneer u een PDF opent die al een JPXDecode-afbeelding bevat, decodeert de engine die stream niet. Het behoudt de JPEG 2000-bytes precies zoals ze zijn, dus een paginakopie of een documentsamenvoeging neemt de afbeelding onaangeraakt mee, byte voor byte. De decoder heeft een enkel ingangspunt, en dat is aan de creatiekant: de bestandsgebaseerde AddImage, uitgezonden op basis van bestandsextensie om .jp2-, .j2k-, .jpt- en .jpc-bronnen af te handelen
Die splitsing is het juiste ontwerp in plaats van een beperking. Het decoderen van een ingebedde JPX-stream bij het laden, om deze bij het opslaan vervolgens opnieuw te coderen, zou een lossless gearchiveerde afbeelding converteren naar een lossy afbeelding en elke samenvoeging opblazen, en dat allemaal voor een afbeelding die u alleen maar van de ene PDF naar de andere wilde verplaatsen. Het letterlijk doorlaten van de stream is een verliesvrije operatie en een snelle. Het decoderen wordt uitgesteld tot het enige moment waarop het werkelijk vereist is: wanneer u de engine een JPEG 2000-bestand vanaf schijf overhandigt en vraagt om die afbeelding te rasteren voor plaatsing op een nieuwe pagina. Op dat moment moet het bestand pixels worden, en wordt de decoder uitgevoerd
Ondersteuning registreren en een afbeelding plaatsen
JPEG 2000-afbeeldingsregistratie is opt-in achter de compile-schakelaar HPDF_REGISTER_JPEG2000_PICTURE, die standaard uit staat. De reden is een echt conflict, geen voorzichtigheid: het globaal registreren van de bestandsformaten jp2, j2k en jpc met TPicture kan interfereren met de BLOB-formaatdetectie waar TppDBImage van ReportBuilder op vertrouwt. Definieer de schakelaar wanneer die integratie niet in het spel is, en de bestandsformaten registreren zich zodat TPicture ze herkent; laat het ongedefinieerd en de AddImage extensie-dispatch decodeert JPEG 2000-bestanden nog steeds direct, omdat dat pad helemaal niet via TPicture loopt
Nu dat begrepen is, is het plaatsen van een JPEG 2000-afbeelding hetzelfde ritme van drie aanroepen als elke andere HotPDF-afbeelding. Geef AddImage een .jp2-pad en een compressietype voor hoe de afbeelding in de uitvoer moet worden opgeslagen, en positioneer vervolgens de geretourneerde afbeeldingsindex op de pagina met 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;
De compressie die u doorgeeft aan AddImage regelt hoe de gedecodeerde afbeelding opnieuw wordt opgeslagen, niet hoe deze is gelezen. Een naar een bitmap gedecodeerd JPEG 2000-bestand kan er weer uit gaan als een DCTDecode JPEG, een Flate-raster of een ander ondersteund filter, afhankelijk van wat het document past. De decodering van JP2 of J2K vindt hoe dan ook eerst plaats, dus dezelfde aanroep accepteert een wavelet-gecomprimeerde bron en bedt deze in in elke vorm die de rest van uw pipeline verwacht
Zie onze notities over rapportuitvoer met lettertypen en afbeeldingen voor het bredere beeld van hoe afbeeldingen en lettertypen in gegenereerde uitvoer belanden. Wanneer het document dat u samenstelt content hergebruikt uit bestaande PDF's, gaat het hier beschreven passthrough-gedrag gepaard met de samenvoegings- en herzieningsmechanismen in objectstreams en incrementele updates. De JPEG 2000-decodeerengine wordt meegeleverd als onderdeel van de HotPDF Component voor Delphi en C++Builder, naast de afbeeldings-, lettertype- en document-API's die elders op deze blog worden behandeld