Ein gescannter medizinischer Objektträger, eine Kachel einer Luftbildvermessung, ein archivierter Filmrahmen mit vollem Dynamikumfang. Dies sind die Bilder, die als JPEG 2000 vorliegen, und das aus gutem Grund. Das Format behält 12 oder 16 Bit pro Kanal bei, komprimiert mit einer Wavelet-Transformation anstelle der bei JPEG verwendeten Block-DCT und kann dasselbe Bild entweder verlustfrei oder verlustbehaftet aus einem einzigen Codestream codieren. Wenn ein aus diesen Quellen erstelltes Dokument zu einem PDF werden muss, muss das Bild einen Filter passieren, den die PDF-Spezifikation genau für diesen Codec reserviert
HotPDF v2.228.0 hat eine funktionierende JPEG 2000-Decodierungs-Engine für diesen Pfad wiederhergestellt. Ein früherer Build hatte die Unit mit Stub-Funktionen ausgeliefert, die nil zurückgaben, sodass die API zwar existierte, aber nichts decodierte. Die aktuelle Engine bindet OpenJPEG 2.5.4 statisch ein und verwandelt eine JP2- oder J2K-Quelle in Pixel, die HotPDF auf einer Seite platzieren kann
Der JPXDecode-Filter in PDF
ISO 32000-1 definiert den JPXDecode-Filter in §7.4.9. Ein PDF-Bild-XObject benennt seine Komprimierung im /Filter-Eintrag des Stream-Wörterbuchs, und JPXDecode ist der Wert, der angibt, dass es sich bei den Stream-Daten um einen JPEG 2000-Codestream handelt und nicht um das Baseline-JPEG, das /DCTDecode überträgt. Der Filter ermöglicht es einem PDF, wavelet-komprimierte Bilddaten mit hoher Farbtiefe aufzunehmen, und er lässt sowohl den verlustfreien als auch den verlustbehafteten Modus des Codecs zu, da der Modus eine Eigenschaft des Codestreams selbst und nicht des ihn umgebenden Wrappers ist
Dieser letzte Punkt ist entscheidend. JPEG 2000 ist ein einziger Algorithmus mit einem verlustfreien Sonderfall, nicht zwei separate Formate. Das reversible 5/3-Wavelet rekonstruiert die Original-Samples exakt; das irreversible 9/7-Wavelet tauscht diese Exaktheit gegen eine kleinere Datei ein. Ein Decoder behandelt beide beim Lesen auf die gleiche Weise, weshalb HotPDF nur einen einzigen Decodierungspfad benötigt, um alles zu akzeptieren, was ein JPXDecode-Stream liefert
Was der Decoder mit den Pixeln macht
PDF-Bild-XObjects erwarten im Normalfall 8 Bit pro Komponente in DeviceGray oder DeviceRGB. JPEG 2000 überschreitet dies routinemäßig, und sein Komponentenmodell ist allgemeiner als ein gepacktes Raster, sodass der Decoder drei Aufgaben erfüllen muss, bevor die Daten als normales Bild verwendbar sind
Erstens werden Komponenten mit hoher Farbtiefe auf 8 Bit neu gesampelt. Ein 12-Bit- oder 16-Bit-Sample wird auf den Bereich von 0 bis 255 herunterskaliert, sodass das Ergebnis ein gewöhnliches 8-Bit-Raster ist. Vorzeichenbehaftete Komponenten werden zunächst in den vorzeichenlosen Bereich verschoben. Dieses Detail ist wichtig, da es an sich verlustbehaftet ist: Ein 16-Bit-Graustufenscan verliert seinen tiefen Tonwertumfang in dem Moment, in dem er zu einem 8-Bit-PDF-Bild wird, was der richtige Kompromiss für die Bildschirm- und Druckausgabe ist, aber nicht für eine erneute Archivierung
Zweitens wird ein YCbCr-Farbraum (der Codec nennt ihn SYCC) in RGB konvertiert. JPEG 2000 speichert Farbe aus Gründen der Komprimierungseffizienz oft in einem Luma-Chroma-Raum, dieselbe Idee, die auch bei Baseline-JPEG zum Einsatz kommt, und der Decoder wendet die standardmäßige inverse Transformation an, sodass die Seite echtes RGB erhält
Drittens werden unterabgetastete Komponenten durch Nearest-Neighbor-Replikation hochskaliert. Chroma-Kanäle werden häufig in halber Auflösung gespeichert, sodass der Decoder jede Komponente mit ihren eigenen Abmessungen und ihrem eigenen Sampling-Faktor liest und dann Samples repliziert, um jeden Kanal auf die volle Bildgröße zu bringen, bevor sie verschachtelt werden. Nearest-Neighbor hält diesen Schritt günstig; das Chroma, das aufgefüllt wird, war ohnehin niederfrequent, sodass die sichtbaren Kosten gering sind
JP2-Boxen im Vergleich zu einem rohen J2K-Codestream
Eine JPEG 2000-Datei gibt es in zwei Formen, und HotPDF erkennt anhand der ersten Bytes und nicht anhand der Dateiendung, welche gelesen wird. Eine JP2-Datei ist ein in Boxen strukturierter Container: Sie beginnt mit der zwölf Byte großen Signaturbox 00 00 00 0C 6A 50 20 20 und verpackt den Codestream zusammen mit Boxen, die Farbraum, Auflösung und Metadaten beschreiben. Ein roher J2K-Codestream enthält überhaupt keinen Container und beginnt mit dem SOC-Marker FF 4F FF 51. Der Decoder liest diese führenden Bytes, erkennt die Signatur und wählt für jeden Fall den passenden OpenJPEG-Codec aus
Beide Formen werden unterstützt, da beide in der Praxis vorkommen. Erfassungsgeräte und Archive, die die seitlichen Metadaten benötigen, geben JP2 aus; Werkzeuge, die die kleinstmögliche Nutzlast wollen, geben den reinen Codestream aus. Der Formattyp ist als Enum modelliert, TJpeg2000FileType, mit den Mitgliedern jtInvalid, jtJP2, jtJ2K und jtJPT. Das JPT-Mitglied benennt die JPIP-Streaming-Variante; der Byte-Signatur-Detektor löst die beiden Formen auf, die er decodieren kann, JP2 und J2K, und meldet alles andere als jtInvalid, sodass eine nicht unterstützte Eingabe sauber fehlschlägt, anstatt Datenmüll zu produzieren
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;
Verlustfrei und verlustbehaftet auf der Codierungsseite
Der Decoder liest beide Modi, ohne dass ihm mitgeteilt wird, um welchen es sich handelt. Die Wahl wird erst dann zu einem Parameter, wenn Sie den anderen Weg gehen und eine JPEG 2000-Datei erzeugen, was HotPDF auch über die Klasse TJpeg2000Bitmap tun kann, einem TBitmap-Nachkommen, der Rasterdaten als JP2 lädt und speichert. Zwei Eigenschaften steuern die Ausgabe. LosslessCompression ist ein Boolean, der bei true das reversible Wavelet auswählt; CompressionQuality ist ein TJpeg2000QualityRange, eine Ganzzahl von 1 bis 100, wobei 1 klein und unansehnlich und 100 groß und originalgetreu ist. Die Standardwerte befinden sich in benannten Konstanten: Jpeg2000DefaultLosslessCompression ist False und Jpeg2000DefaultLossyQuality ist 80
Die Entscheidung ist inhaltlicher Natur. Verlustfrei eignet sich für eine Masterkopie, einen medizinischen oder rechtlichen Scan, also alles, was später möglicherweise neu codiert wird und keinen Generationsverlust ansammeln darf. Verlustbehaftet bei Qualität 80 eignet sich für ein Bild, das für den Bildschirm oder Druck bestimmt ist, wo die anmutige Verschlechterung des Wavelets eine spürbar kleinere Datei liefert, ohne dass ein Leser Artefakte bemerken würde. Ein CMYK-Vorbehalt muss beachtet werden: Die Bitmap stellt SetCMYK zur Verfügung, um vierkanalige Daten als CMYK statt als RGBA zu markieren, was für Druckpipelines wichtig ist, die Farbauszüge intakt halten
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;
Warum es keine Decode-on-Load-Filterpipeline gibt
Eine architektonische Tatsache bestimmt, wie Sie dies nutzen, und es ist leicht, das Gegenteil anzunehmen. HotPDF hat keinen allgemeinen Decode-on-Load-Bildfilter. Wenn Sie ein PDF öffnen, das bereits ein JPXDecode-Bild enthält, decodiert die Engine diesen Stream nicht. Sie behält die JPEG 2000-Bytes genau so bei, wie sie sind, sodass eine Seitenkopie oder eine Dokumentenzusammenführung das Bild unverändert, Byte für Byte, überträgt. Der Decoder hat einen einzigen Einstiegspunkt, und der liegt auf der Erstellungsseite: das dateibasierte AddImage, das nach Dateiendung verteilt wird, um .jp2-, .j2k-, .jpt- und .jpc-Quellen zu verarbeiten
Diese Trennung ist das korrekte Design und keine Einschränkung. Das Decodieren eines eingebetteten JPX-Streams beim Laden, nur um ihn beim Speichern wieder neu zu codieren, würde ein verlustfreies archiviertes Bild in ein verlustbehaftetes umwandeln und jeden Zusammenführungsvorgang aufblähen, und das alles für ein Bild, das Sie nur von einem PDF in ein anderes verschieben wollten. Das unveränderte Durchreichen des Streams ist eine verlustfreie und schnelle Operation. Das Decodieren wird auf den einzigen Moment verschoben, in dem es wirklich erforderlich ist: wenn Sie der Engine eine JPEG 2000-Datei von der Festplatte übergeben und sie anweisen, dieses Bild zur Platzierung auf einer neuen Seite zu rastern. In diesem Moment muss die Datei zu Pixeln werden, und der Decoder wird ausgeführt
Unterstützung registrieren und ein Bild platzieren
Die Registrierung von JPEG 2000-Bildern ist ein Opt-in hinter dem Compilerschalter HPDF_REGISTER_JPEG2000_PICTURE, der standardmäßig deaktiviert ist. Der Grund dafür ist ein echter Konflikt, keine Vorsicht: Die globale Registrierung der Dateiformate jp2, j2k und jpc bei TPicture kann die Erkennung von BLOB-Formaten stören, auf die sich das TppDBImage des ReportBuilders verlässt. Definieren Sie den Schalter, wenn diese Integration keine Rolle spielt, und die Dateiformate registrieren sich, sodass TPicture sie erkennt; lassen Sie ihn undefiniert, und der Erweiterungs-Verteiler von AddImage decodiert JPEG 2000-Dateien dennoch direkt, da dieser Pfad überhaupt nicht über TPicture verläuft
Unter dieser Voraussetzung erfolgt die Platzierung eines JPEG 2000-Bildes im gleichen Drei-Aufruf-Rhythmus wie bei jedem anderen HotPDF-Bild. Übergeben Sie AddImage einen .jp2-Pfad und einen Komprimierungstyp dafür, wie das Bild in der Ausgabe gespeichert werden soll, und positionieren Sie dann den zurückgegebenen Bildindex mit ShowImage auf der Seite
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;
Die Komprimierung, die Sie an AddImage übergeben, steuert, wie das decodierte Bild wieder gespeichert wird, nicht, wie es gelesen wurde. Eine zu einer Bitmap decodierte JPEG 2000-Datei kann als DCTDecode-JPEG, als Flate-Raster oder mit einem anderen unterstützten Filter wieder ausgegeben werden, je nachdem, was zum Dokument passt. Die Decodierung aus JP2 oder J2K erfolgt ohnehin zuerst, sodass derselbe Aufruf eine wavelet-komprimierte Quelle akzeptiert und sie in der Form einbettet, die der Rest Ihrer Pipeline erwartet
Einen breiteren Überblick darüber, wie Bilder und Schriftarten in generierten Ausgaben landen, finden Sie in unseren Hinweisen zur Berichtsausgabe mit Schriftarten und Bildern. Wenn das von Ihnen erstellte Dokument Inhalte aus vorhandenen PDFs wiederverwendet, lässt sich das hier beschriebene Passthrough-Verhalten mit den Zusammenführungs- und Revisionsmechanismen unter Objekt-Streams und inkrementelle Aktualisierungen kombinieren. Die JPEG 2000-Decodierungs-Engine ist als Teil der HotPDF-Komponente für Delphi und C++Builder verfügbar, zusammen mit den Bild-, Schriftarten- und Dokument-APIs, die an anderer Stelle in diesem Blog behandelt werden