Une diapositive médicale numérisée, une tuile de relevé aérien, une image de film archivée sur toute sa plage dynamique. Ce sont ces images qui arrivent sous le format JPEG 2000, et ce n'est pas sans raison. Le format conserve 12 ou 16 bits par canal, compresse avec une transformée par ondelettes au lieu de la DCT par blocs qu'utilise JPEG, et peut encoder la même image avec ou sans perte à partir d'un seul flux de code. Lorsqu'un document construit à partir de ces sources doit devenir un PDF, l'image doit traverser un filtre que la spécification PDF réserve exactement à ce codec
HotPDF v2.228.0 a restauré un moteur de décodage JPEG 2000 fonctionnel pour ce processus. Une version antérieure avait livré l'unité avec des fonctions factices (stubs) qui renvoyaient nil, de sorte que l'API existait mais ne décodait rien. Le moteur actuel lie OpenJPEG 2.5.4 de manière statique et transforme une source JP2 ou J2K en pixels que HotPDF peut placer sur une page
Le filtre JPXDecode dans PDF
L'ISO 32000-1 définit le filtre JPXDecode au §7.4.9. Un XObject d'image PDF nomme sa compression dans l'entrée /Filter du dictionnaire de flux, et JPXDecode est la valeur qui indique que les données du flux correspondent à un flux de code JPEG 2000 plutôt qu'au JPEG de base transporté par /DCTDecode. C'est ce filtre qui permet à un PDF de contenir des données d'image compressées par ondelettes avec une profondeur de bits élevée, et il admet à la fois les modes sans perte et avec perte du codec, car le mode est une propriété du flux de code lui-même et non de l'enveloppe qui l'entoure
Ce dernier point est celui qu'il faut retenir. JPEG 2000 est un algorithme unique avec un cas particulier sans perte, et non deux formats distincts. L'ondelette réversible 5/3 reconstruit exactement les échantillons originaux ; l'ondelette irréversible 9/7 échange cette exactitude contre un fichier plus petit. Un décodeur traite les deux de la même manière lors de la lecture, c'est pourquoi HotPDF ne nécessite qu'un seul chemin de décodage pour accepter tout ce qu'un flux JPXDecode lui envoie
Ce que le décodeur fait aux pixels
Dans le cas le plus courant, les XObjects d'image PDF s'attendent à 8 bits par composant en DeviceGray ou DeviceRGB. JPEG 2000 dépasse régulièrement cela, et son modèle de composants est plus général qu'une trame compressée, le décodeur a donc trois tâches à accomplir avant que les données ne soient utilisables comme une image normale
Premièrement, les composants à forte profondeur de bits sont rééchantillonnés sur 8 bits. Un échantillon de 12 ou 16 bits est réduit à l'échelle de la plage de 0 à 255 de sorte que le résultat soit une trame 8 bits ordinaire. Les composants signés sont d'abord décalés dans la plage non signée. Le détail a son importance car il est lui-même source de perte : une numérisation en niveaux de gris 16 bits perd sa gamme tonale profonde au moment où elle devient une image PDF 8 bits, ce qui constitue le bon compromis pour une sortie sur écran ou pour l'impression, mais pas pour le ré-archivage
Deuxièmement, un espace colorimétrique YCbCr (le codec l'appelle SYCC) est converti en RVB. JPEG 2000 stocke souvent les couleurs dans un espace luma-chroma pour des raisons d'efficacité de compression, la même idée que celle utilisée par le JPEG de base, et le décodeur applique la transformation inverse standard pour que la page reçoive du vrai RVB
Troisièmement, les composants sous-échantillonnés sont suréchantillonnés par la méthode de réplication du plus proche voisin. Les canaux de chrominance sont fréquemment stockés à mi-résolution, de sorte que le décodeur lit chaque composant avec ses propres dimensions et son propre facteur d'échantillonnage, puis réplique les échantillons pour amener chaque canal à la taille de l'image complète avant l'entrelacement. Le plus proche voisin maintient un faible coût pour cette étape ; la chrominance qu'il remplit était de toute façon à basse fréquence, le coût visible est donc faible
Boîtes JP2 contre flux de code J2K brut
Un fichier JPEG 2000 se présente sous deux formes, et HotPDF détecte laquelle il lit à partir des premiers octets plutôt qu'à partir de l'extension de fichier. Un fichier JP2 est un conteneur structuré en boîtes : il s'ouvre par la boîte de signature de douze octets 00 00 00 0C 6A 50 20 20 et enveloppe le flux de code aux côtés de boîtes décrivant l'espace colorimétrique, la résolution et les métadonnées. Un flux de code J2K brut ne comporte aucun conteneur du tout et commence par le marqueur SOC FF 4F FF 51. Le décodeur lit ces octets de tête, reconnaît la signature et sélectionne le codec OpenJPEG correspondant pour chaque cas
Les deux formes sont prises en charge car elles se rencontrent toutes les deux dans la nature. Les appareils de capture et les archives qui ont besoin des métadonnées latérales émettent du JP2 ; les outils qui souhaitent la charge utile la plus petite possible émettent le flux de code nu. Le type de format est modélisé sous la forme d'une énumération, TJpeg2000FileType, avec les membres jtInvalid, jtJP2, jtJ2K et jtJPT. Le membre JPT nomme la variante de streaming JPIP ; le détecteur de signature d'octets résout les deux formes qu'il peut décoder, JP2 et J2K, et signale toute autre chose comme jtInvalid afin qu'une entrée non prise en charge échoue proprement au lieu de produire des données inutilisables
uses
HPDFJpeg2000;
var
Decoder: THPDFJpeg2000Decoder;
Pixels: TJpeg2000ByteArray;
begin
Decoder := THPDFJpeg2000Decoder.Create;
try
if Decoder.LoadFromStream(Input) then // JP2 ou J2K, détecté automatiquement
if Decoder.GetImageData(Pixels) then
// Pixels est entrelacé en 8 bits, d'une largeur de ColorComponents canaux,
// avec la ligne principale de haut en bas : prêt pour un XObject DeviceGray/DeviceRGB.
ProcessRaster(Decoder.Width, Decoder.Height,
Decoder.ColorComponents, Pixels);
finally
Decoder.Free;
end;
end;
Sans perte et avec perte du côté encodage
Le décodeur lit les deux modes sans qu'on lui dise de quel mode il s'agit. Le choix ne devient un paramètre que lorsque l'on procède dans l'autre sens pour produire un fichier JPEG 2000, ce que HotPDF peut également faire via la classe TJpeg2000Bitmap, un descendant de TBitmap qui charge et enregistre des données raster en tant que JP2. Deux propriétés régissent la sortie. LosslessCompression est un booléen qui sélectionne l'ondelette réversible lorsqu'il est défini sur true ; CompressionQuality est une TJpeg2000QualityRange, un entier de 1 à 100 où 1 est petit et laid et 100 est volumineux et fidèle. Les valeurs par défaut vivent dans des constantes nommées : Jpeg2000DefaultLosslessCompression est à False et Jpeg2000DefaultLossyQuality est de 80
Cette décision relève du contenu. Le sans perte (Lossless) convient à une copie maître, une numérisation médicale ou légale, toute chose qui pourrait être réencodée plus tard et qui ne doit pas accumuler de perte générationnelle. L'option avec perte (Lossy) avec une qualité de 80 convient à une image destinée à l'écran ou à l'impression, où la dégradation progressive de l'ondelette donne un fichier sensiblement plus petit sans aucun artefact qu'un lecteur pourrait remarquer. Il y a une mise en garde CMJN à signaler : le bitmap expose SetCMYK pour marquer les données à quatre canaux comme du CMJN plutôt que du RVBA, ce qui importe pour les pipelines d'impression qui conservent les séparations intactes
uses
HPDFJpeg2000;
var
Bmp: TJpeg2000Bitmap;
begin
Bmp := TJpeg2000Bitmap.Create;
try
Bmp.LoadFromStream(Source); // décode un JP2/J2K existant
Bmp.LosslessCompression := True; // ondelette réversible 5/3
// ou, pour un fichier plus petit avec perte :
// Bmp.LosslessCompression := False;
// Bmp.CompressionQuality := 80; // correspond à la valeur par défaut
Bmp.SaveToStream(Output); // écrit toujours un fichier JP2
finally
Bmp.Free;
end;
end;
Pourquoi il n'y a pas de pipeline de filtre de décodage au chargement
Un fait architectural façonne la manière dont vous utilisez tout ceci, et il est facile de supposer l'inverse. HotPDF ne possède pas de filtre d'image général de décodage au chargement. Lorsque vous ouvrez un PDF qui contient déjà une image JPXDecode, le moteur ne décode pas ce flux. Il conserve les octets JPEG 2000 exactement tels qu'ils sont, de sorte qu'une copie de page ou une fusion de documents transporte l'image intacte, octet par octet. Le décodeur possède un seul point d'entrée, et il se trouve du côté de la création : l'appel basé sur fichier AddImage, réparti par extension de fichier pour gérer les sources .jp2, .j2k, .jpt et .jpc
Cette séparation constitue la conception correcte plutôt qu'une limitation. Décoder un flux JPX intégré au chargement, pour ensuite le ré-encoder lors de l'enregistrement, convertirait une image archivée sans perte en une image avec perte et gonflerait chaque fusion, tout cela pour une image que vous aviez seulement l'intention de déplacer d'un PDF à un autre. Faire passer le flux tel quel est une opération sans perte et rapide. Le décodage est différé au seul moment où il est véritablement requis : lorsque vous transmettez au moteur un fichier JPEG 2000 à partir du disque et que vous lui demandez de rastériser cette image pour la placer sur une nouvelle page. À ce stade, le fichier doit devenir des pixels, et le décodeur entre en action
Enregistrer la prise en charge et placer une image
L'enregistrement des images JPEG 2000 est une option (opt-in) derrière le commutateur de compilation HPDF_REGISTER_JPEG2000_PICTURE, qui est désactivé par défaut. La raison est un réel conflit, et non de la prudence : l'enregistrement global des formats de fichiers jp2, j2k et jpc avec TPicture peut interférer avec la détection du format BLOB sur laquelle repose TppDBImage de ReportBuilder. Définissez le commutateur lorsque cette intégration n'est pas en jeu, et les formats de fichiers s'enregistrent afin que TPicture les reconnaisse ; laissez-le non défini et la répartition de l'extension AddImage décode toujours les fichiers JPEG 2000 directement, car ce chemin ne passe pas du tout par TPicture
Ceci étant compris, le placement d'une image JPEG 2000 suit le même rythme de trois appels que n'importe quelle autre image HotPDF. Fournissez à AddImage un chemin .jp2 et un type de compression pour déterminer comment l'image doit être stockée dans la sortie, puis positionnez l'index d'image retourné sur la page avec ShowImage
var
Pdf: THotPDF;
ImgIndex: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.AddPage;
// La source .jp2 est décodée via le backend OpenJPEG, puis
// ré-intégrée avec la compression que vous demandez ici.
ImgIndex := Pdf.AddImage('Scan_16bit.jp2', icJpeg);
// x, y, largeur, hauteur en points ; le 0 final est l'angle de rotation.
Pdf.ShowImage(ImgIndex, 72, 72, 400, 300, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
La compression que vous passez à AddImage contrôle comment l'image décodée est de nouveau stockée, et non comment elle a été lue. Un fichier JPEG 2000 décodé en un bitmap peut ressortir en tant que JPEG DCTDecode, trame Flate, ou un autre filtre pris en charge, selon ce qui convient au document. Le décodage à partir de JP2 ou J2K se produit en premier indépendamment, de sorte que le même appel accepte une source compressée par ondelettes et l'intègre sous la forme que le reste de votre pipeline attend
Pour une vue d'ensemble de la façon dont les images et les polices atterrissent dans la sortie générée, consultez nos notes sur la sortie de rapports avec des polices et des images. Lorsque le document que vous assemblez réutilise du contenu de PDF existants, le comportement de transmission (passthrough) décrit ici s'associe aux mécaniques de fusion et de révision des flux d'objets et des mises à jour incrémentielles. Le moteur de décodage JPEG 2000 est livré dans le cadre du Composant HotPDF pour Delphi et C++Builder, à côté des API d'image, de police et de document abordées ailleurs sur ce blog