Utilisation de LibTiff avec Delphi

Eric SIBERT

Exemple d'utilisation de LibTiff avec Delphi : réduire en 256 couleurs optimales une grande image Tiff.

- Titre : Utilisation de LibTiff avec Delphi

- Auteur : Eric SIBERT

- Parution : 28 mai 2005

- Mise à jour : 7 décembre 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2005 Eric SIBERT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. II. Détermination de la palette

- III. III. Dans le code

- IV. IV. Conclusion

I - Introduction

Le format d'image Tiff est assez répandu et on peut trouver des composants Delphi pour lire, voire écrire des images à ce format. Toutefois, dans certains cas, comme la manipulation de grandes images, on peut vouloir accéder directement au contenu de l'image sans la charger entièrement en mémoire comme le fait un composant. Il convient alors d'utiliser des outils de plus bas niveau. Compte tenu du nombre de variantes que comprend le format, il n'est pas envisageable d'aller lire directement le fichier, contrairement au Bitmap par exemple. Il existe par contre une bibliothèque adaptée à ce type de travail : LibTiff. Cette bibliothèque est disponible en Open Source… et en C. Heureusement, Joris Van Damme a réalisé une traduction des entêtes de LibTiff pour Delphi.

Dans la suite de cet article, je vais vous montrer comment se servir de cette bibliothèque pour réduire de 16 millions à 256 le nombre de couleurs d'une image a priori grande. En essayant d'obtenir une palette de bonne qualité. Cette opération s'appelle aussi une quantification de palette.

II - II. Détermination de la palette

Sur le principe de base, il y a un article chez Microsoft. Grosso modo, dans l'espace 3D des couleurs, on constitue des boîtes. Une boîte est définie par ses limites RougeMin, RougeMax, VertMin, VertMax, BleuMin, BleuMax. Chaque boîte contient une partie des couleurs présentes dans l'image avec leur fréquence d'apparition. Au début, on a une seule boîte englobant l'ensemble des couleurs. On recherche le plus grand côté de la boîte. On découpe la boîte perpendiculairement à cette direction, au niveau de la valeur moyenne des couleurs qu'elle contient. Par exemple, on constate que la boîte contient des rouges de 3 à 249, des bleus de 25 à 230 et des verts de 50 à 240. Le rouge constitue le plus grand côté. On trouve par ailleurs que la valeur moyenne des rouges contenus dans la boîte est de 118,3. On va découper la boîte en deux boîtes plus petites, l'une avec les rouges de 3 à 118 et l'autre de 119 à 249. Puis on recommence l'opération sur les nouvelles boîtes. On cherche sur l'ensemble des boîtes celle qui est la plus allongée. Et on la découpe suivant le même principe. On répète l'opération jusqu'à avoir autant de boîtes que de couleurs voulues (256 ou 236 … ou 23). Chaque boîte représente une entrée dans la palette. On prend la valeur moyenne des pixels de la boîte pour définir la couleur de la palette. Il ne reste plus qu'à parcourir l'image et à déterminer pour chaque pixel dans quelle boîte il se trouve. Le numéro de la boîte est alors le numéro dans la palette. Le pixel qui est dans la boîte 53 aura la couleur 53 de la palette.

À chaque fois qu'on veut découper une boîte, il faut déterminer la valeur moyenne de la couleur correspondante. Par exemple, en parcourant l'image et en repérant les pixels dont la couleur est dans la boîte puis en les ajoutant au calcul de la moyenne. À répéter par le nombre de couleurs qu'on veut obtenir. Ce qui pour une image importante, voire stockée sur un disque dur, risque d'être très long. Pour contourner ce problème, j'ai fait un tableau avec les 16 millions de couleurs possibles et je ne parcours l'image qu'une fois pour noter la fréquence d'apparition de chaque couleur. J'utilise ensuite ce tableau pour faire les calculs sur les boîtes. L'allocation d'un tableau de 16 millions d'entiers (64 Mo) n'est certainement pas optimale pour une petite image. La méthode ne devient intéressante que quand la taille de l'image dépasse celle du tableau.

III - III. Dans le code

Après avoir téléchargé la bibliothèque, on l'appelle dans notre projet :

uses
LibTiffDelphi;

Pour la lecture d'une image Tiff :

var
Source  : PTiff;
BitsPerSampleValue: Word;
Width, Height : Cardinal;
SourceLine, DestLine : array of byte;
I, J : integer;
begin
// first step
Source:=TIFFOpen(FileName,'r');
assert(Source<>nil,'Source File Missing');
try
// checking file format
assert(TIFFGetField(Source,TIFFTAG_BITSPERSAMPLE,@BitsPerSampleValue)=1,
'Corrupted Source File with TIFFTAG_BITSPERSAMPLE');
assert(BitsPerSampleValue=8,'Source File is not a 8bit color depth');
assert(TIFFGetField(Source,TIFFTAG_SAMPLESPERPIXEL,@BitsPerSampleValue)=1,
'Corrupted Source File with TIFFTAG_SAMPLESPERPIXEL');
assert(BitsPerSampleValue=3,'Source File as not 3 samples per value');
// reading sizes
assert(TIFFGetField(Source,TIFFTAG_IMAGEWIDTH,@Width)=1,'Corrupted Source file with TIFFTAG_IMAGEWIDTH');
assert(TIFFGetField(Source,TIFFTAG_IMAGELENGTH,@Height)=1,'Corrupted Source file with TIFFTAG_IMAGELENGTH');
setLength(SourceLine,3*Width);

// calculating the density
FillChar(Density[0,0,0],16777216,0);
for J:=0 to Height-1 do
begin
TIFFReadScanline(Source,@(SourceLine[0]),J,0);
for I:=0 to Width-1 do
inc(Density[SourceLine[3*I],SourceLine[3*I+1],SourceLine[3*I+2]]);
end;
[...]
finally
TIFFClose(Source);
end;

Dans l'ordre, nous avons ouverture du fichier, récupération d'informations qui sont stockées dans les tags de l'image. Puis lecture du contenu de l'image de façon similaire au scanline d'un bitmap. On se retrouve presque à lire une image Tiff comme une image Bitmap.

Je vous passe les détails sur la détermination de la palette. Vous irez voir dans le code source.

Pour l'écriture d'une image Tiff :

// creating the tiff file
Destination:=TIFFOpen(DestFileName,'w');
assert(Destination<>nil,'Can''t create Destination file');
try
assert(TIFFSetField(Destination,TIFFTAG_IMAGEWIDTH,Width)=1,'Problem with TIFFTAG_IMAGEWIDTH');
assert(TIFFSetField(Destination,TIFFTAG_IMAGELENGTH,Height)=1,'Problem with TIFFTAG_IMAGELENGTH');
assert(TIFFSetField(Destination,TIFFTAG_BITSPERSAMPLE,8)=1,'Problem with TIFFTAG_BITSPERSAMPLE');
assert(TIFFSetField(Destination,TIFFTAG_SAMPLESPERPIXEL,1)=1,'Problem with TIFFTAG_SAMPLESPERPIXEL');
assert(TIFFSetField(Destination,TIFFTAG_ROWSPERSTRIP,Height)=1,'Problem with TIFFTAG_ROWSPERSTRIP');
assert(TIFFSetField(Destination,TIFFTAG_COMPRESSION,COMPRESSION_NONE)=1,
'Problem with TIFFTAG_COMPRESSION');
assert(TIFFSetField(Destination,TIFFTAG_PHOTOMETRIC,PHOTOMETRIC_PALETTE)=1,
'Problem with TIFFTAG_PHOTOMETRIC');
assert(TIFFSetField(Destination,TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)=1,
'Problem with TIFFTAG_PLANARCONFIG');

// setting the palette
// palette must contain 256 colors (2^BitPerSample)
SetLength(Red_Tbl,256);
SetLength(Green_Tbl,256);
SetLength(Blue_Tbl,256);
for I:=0 to PaletteBuilder.NbColor-1 do
begin
MyColor:=PaletteBuilder.Color[I];
Red_Tbl[I]:=MyColor.X*256;
Green_Tbl[I]:=MyColor.Y*256;
Blue_Tbl[I]:=MyColor.Z*256;
end;
assert(TIFFSetField(Destination,TIFFTAG_COLORMAP, @(red_tbl[0]), @(green_tbl[0]),
@(blue_tbl[0]))=1,'Problem setting the ColorMap');

PaletteBuilder.TableToPalette(Density);

// transferring the picture itself
setLength(DestLine,Width);
for J:=0 to Height-1 do
begin
TIFFReadScanline(Source,@(SourceLine[0]),J,0);
for I:=0 to Width-1 do
DestLine[I]:=Density[SourceLine[3*I],SourceLine[3*I+1],SourceLine[3*I+2]];
TIFFWriteScanline(Destination,@(DestLine[0]),J,0);
end;

finally
TiffClose(Destination);
end;

Création de l'objet toujours sans problème. Là, il y a un peu plus de tags à fournir. Il faut enregistrer la palette dans le fichier Tiff. Et finir en enregistrant l'image elle-même toujours avec un scanline.

IV - IV. Conclusion

Le programme utilisé ici comme exemple me permet de réduire en 256 couleurs des vues satellites que j'ai générées avec un autre logiciel personnel. Le temps de calcul sur une image de 18000x16000 (800Mo) est de 13 minutes quand Photoshop a besoin d'une demi-heure et que Gimp n'arrive même pas à ouvrir l'image. Le résultat, en termes de qualité d'image, est tout à fait acceptable. Pour tenir compte de la sensibilité de l'œil, il serait sans doute plus judicieux de travailler dans les espaces YCrCb ou TSV (au lieu de RVB) et de compter double les longueurs sur l'axe de la luminosité (Y ou T suivant le modèle).

Sur l'utilisation du Tiff, la traduction pour Delphi de LibTiff permet une gestion aisée des images Tiff. On peut aussi accéder à tous les tags contenus dans les images Tiff. Je suis par exemple en train de travailler à la récupération des tags GeoTiff pour l'étalonnage des vues spatiales et aériennes.

Le code source : source_tiff256.zip (5 ko).