Utilisation de LibTiff avec Delphi

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

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Le format d'image Tiff est assez répandu et on peut trouver des composants Delphi pour lire voir é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 librairie adaptée à ce type de travail : LibTiff. Cette librairie 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 librairie pour réduire de 16 millions à 256 le nombre de couleurs d'une image à 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. A répéter par le nombre de couleur qu'on veut obtenir. Ce qui pour une image importante, voir 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 :

 
Sélectionnez
uses
  LibTiffDelphi;

Pour la lecture d'une image Tiff :

 
Sélectionnez
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 :

 
Sélectionnez
  // 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 satellite 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 terme 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).

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Liste de mes articles :
Types énumérés, intervalle et ensemble
MMX avec Delphi 6 / Kylix
Préchargement de données dans le cache
MMX ( 2 ) avec Delphi 6 / Kylix
Instructions SIMD sur les réels
Internationaliser un projet Delphi
Installer D6 sous Windows 95
Tramage d'une image
Coder le PNG soi-même
Utiliser LibTiff avec Delphi
Ecrire une UDF FireBird avec Kylix

  

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.