Auteur : Patrick Biker
Date : 12 Nov 2005
Révision: 14 nov 2005
WinDev 10 permettra d'accéder en WLangage aux données EXIF d'une image numérique. Cette nouvelle fonctionnalité est sans doute la conséquence d'une demande des utilisateurs dans ce domaine. Dans cette page, nous verrons comment accéder aux informations EXIF d'une image numérique, dans le but précis d'extraire la vignette (thumbnail en anglais).
Une photo numérique au format DCF (Digital Camera File), donc type JPEG, contient des informations sur la photo : Taille, dimension, marque et modèle de l'appareil photo, présence d'une vignette, etc. L'encodage de ces informations est réalisé au format EXIF, qui sera décrit plus loin.
Ce document s'intéresse à la programmation en WLangage de la lecture de la vignette.
Une photo numérique est d'abord un fichier binaire dont la structure est la suivante :
Pour accéder à la vignette, il faut lire la première structure de répertoire (0th IFD). Elle contient un pointeur vers la deuxième structure de répertoire (1st IFD). Cette structure contient une pointeur vers les données brutes de la vignette.
Le code WLangage donné ici peut être testé dans une fenêtre WinDev très simple qui contiendra un bouton et un champ Image, nommé ImageVignette.
Tout ce qui suit concerne le code du bouton. Tout commence par l'ouverture du fichier.
IdFichier = fOuvre("cat.jpg", foLecture) SI IdFichier = -1 ALORS RETOUR
Le code se terminera par la fermeture du fichier. C'est une bonne habitude d'écrire immédiatement ce traitement, pour éviter de l'oublier
fFerme(IdFichier)
Démarrons maintenant la lecture du fichier. Un fichier DCF valide débute par une entête de fichier JPEG de 12 octets de long. Nous utiliserons une chaine fixe en guise de buffer de lecture.
sBuffer est une chaîne fixe sur 12 SI 12 <> fLit(IdFichier, 12, &sBuffer) ALORS GOTO _Erreur
Pour garder un code simple, notre gestion d'erreur est minimale. Elle consiste à sauter (GOTO) vers l'instruction qui ferme le fichier. Une autre manière de procéder consiste par exemple à définir une "exception utilisateur".
Un minimum de vérification est à réaliser sur les données lues. Il s'agit de valider la structure du fichier JPEG.
Dans un fichier JPEG, les 4 premiers octet doivent être FFD8FFE1. Nous utilisons ici la notation hexadécimale. FFD8 est le marqueur SOI (Start Of Image). FFE1 est le marqueur APP1.
Nous allons donc construire une valeur de contrôle qui sera comparée au 4 premiers caractères du buffer lu. Le WLangage permet d'écrire des données en hexadécimal, ce qui nous évitera une conversion.
sControle est une chaîne fixe sur 4 sControle = Caract(0xFF)+Caract(0xD8)+Caract(0xFF)+Caract(0xE1) SI sBuffer[[1 A 4]] <> sControle ALORS GOTO _Erreur
Nous contrôlons également que l'entête contient bien "Exif" en toutes lettres en position 7 dans le buffer.
SI Position(sBuffer, "Exif") <> 7 ALORS GOTO _Erreur
Immédiatement après l'entête du fichier JPEG débute un nouveau bloc de données, l'entête TIFF composé de 8 octets. La position de cette entête TIFF servira de référence par la suite pour calculer d'autres positions dans le fichier. Nous utilisons le même buffer pour lire les données.
SI 8 <> fLit(IdFichier, 8, &sBuffer) ALORS GOTO _Erreur
Ce bloc nous renseigne sur le format de stockage des nombres dans le fichier DCF. Deux codages existent : Intel et Motorola. Dans le codage Intel, les octets composant un entier sont placés dans l'ordre poids faible - poids fort.
Par exemple, selon que notre fichier JPEG utilise le codage Intel ou Motorola, la valeur A1C6 (41414) sera décrite par deux octets consécutifs ayant pour valeurs :
Les ordinateurs de type PC utilise le codage Intel (même si le processeur est AMD). Il sera donc plus facile de traiter un fichier codé en Intel.
L'entête TIFF débute par les caractères "II" si le codage est Intel, et par "MM" si le codage est Motorola.
CodageMotorola est un booléen CodageMotorola = (sBuffer[[1 A 2]] = "MM")
Juste après l'information de codage, on trouve 2 octets avec la valeur constante 0x002A. Remarquons au passage que si le codage est Intel, on trouvera Ox2A00.
Les 4 octets suivant codent un décalage (offset en anglais) permettant de trouver la première structure de répertoire (IFD) du fichier. Rapellons-nous que cette structure contient elle-même un pointeur vers une autre structure (IFD) qui décrit la vignette.
Dans la majorité des cas, la structure de répertoire (IFD) vient immédiatement après l'entête TIFF. Le décalage, qui se mesure depuis le début de l'entête TIFF vaut donc 8 (l'entête TIFF est composée de 8 octets). Pour décoder ce décalage, il faudra procéder différemment selon que le codage est Intel ou Motorola.
Si le codage est Intel, il est possible d'affecter directement sBuffer[[5 à 8]] dans un entier, si le codage est Motorola, il faudra inverser tous les octets. Nous utiliserons pour cela la fonction ConversionMotorola qui recoit un entier sur 2 ou 4 octets et renvoie le même entier après avoir inversé tous ces octets. Le code de cette fonction est donné plus loin.
Pour affecter l'entier "decalage" on utilise la commande Transfer() qui réalise une copie de mémoire à mémoire.
decalage est un entier SI CodageMotorola ALORS Transfer(&decalage, (&sBuffer + 4), 4) decalage = ConversionMotorola(decalage) SINON Transfer(&decalage, (&sBuffer + 4), 4) FIN
Si le décalage vaut 8. Le bloc IFD suivant suit immédiatement. Dans le cas contraire, il faut se positionner dessus.
SI decalage > 8 ALORS fPositionne(IdFichier, 12 + decalage, fpDébut) FIN
Chaque structure de répertoire débute par l'indication du nombre d'éléments. Cette valeur occupe 2 octets.
nbElement est un entier sur 2 octets SI 2 <> fLit(IdFichier, 2, &nbElement) ALORS GOTO _Erreur SI CodageMotorola ALORS nbElement = ConversionMotorola(nbElement)
Chaque élément du répertoire fait 12 octets. On va sauter ces élément pour aller directement au pointeur.
fPositionne(IdFichier, 12 * nbElement, fpCourant)
Le pointeur sur la vignette est une valeur sur 4 octets. C'est un décalage vers une nouvelle structure de répertoire (IFD) qui décrit la vignette.
SI 4 <> fLit(IdFichier, 4, &decalage) ALORS GOTO _Erreur SI CodageMotorola ALORS decalage = ConversionMotorola(decalage) SI decalage = 0 ALORS GOTO _Erreur fPositionne(IdFichier, 12 + decalage, fpDébut)
Cette structure débute aussi par l'indication du nombre d'éléments.
SI 2 <> fLit(IdFichier, 2, &nbElement) ALORS GOTO _Erreur SI CodageMotorola ALORS nbElement = ConversionMotorola(nbElement)
Ici, il faut lire tous les éléments pour récupérer la taille, et la position de la vignette dans le fichier (décalage). Chaque élément a une longeur de 12 octets. Il est composé de 4 parties : Tag (2 octets), Format (2 octets), composant (4 octets), et enfin données (4 octets).
L'élément contenant la taille est désigné par le Tag 0x0202. L'élément contenant le décalage est désigné par le Tag 0x0201.
tailleVignette est un entier BOUCLE (nbElement) SI 12 <> fLit(IdFichier, 12, &sBuffer) ALORS GOTO _Erreur SI sBuffer[[1 A 2]] = Caract(2)+Caract(2) ALORS Transfer(&tailleVignette, (&sBuffer + 8), 4) SI CodageMotorola ALORS tailleVignette = ConversionMotorola(tailleVignette) FIN SI CodageMotorola ALORS SI sBuffer[[1 A 2]] = Caract(2)+Caract(1) ALORS Transfer(&decalage, (&sBuffer + 8), 4) decalage = ConversionMotorola(decalage) FIN SINON SI sBuffer[[1 A 2]] = Caract(1)+Caract(2) ALORS Transfer(&decalage, (&sBuffer + 8), 4) FIN FIN FIN
La présence d'une vignette n'est pas systématique. Si la vignette est absente, le décalage vaudra zéro. Dans le cas contraire, on va extraire la vignette et créer un fichier JPEG sur le disque.
SI decalage = 0 ALORS GOTO _Erreur fPositionne(IdFichier, 12 + decalage, fpDébut) sBufferVignette est une chaîne sBufferVignette = Répète(Caract(0), tailleVignette) SI tailleVignette <> fLit(IdFichier, tailleVignette, &sBufferVignette) ALORS GOTO _Erreur fSauveTexte("vignette.jpg", sBufferVignette)
La vignette est maintenant sauvée sur le disque. Nous allons l'afficher dans un champ image.
ImageVignette = "" ImageVignette..Largeur = ExtraitChaîne(InfoBitmap("vignette.jpg"),2) ImageVignette..Hauteur = ExtraitChaîne(InfoBitmap("vignette.jpg"),3) ImageVignette = "vignette.jpg"
C'est fini ! Il nous reste à ferme le fichier.
fFerme(IdFichier) RETOUR _Erreur: Erreur("Il y a une erreur") fFerme(IdFichier)
A la demande générale, un code source plus accessible pour le copier/coller.
Source en WinDev 9 à télécharger
Dans le cas d'un fichier au format Motorola, cette fonction inverse les octets afin de les remettre dans le format Intel.
PROCEDURE ConversionMotorola(paramEntier2ou4) tbOctet est un tableau de 4 entiers sans signe sur 1 octet en2 est un entier sans signe sur 2 octet en4 est un entier sans signe sur 4 octets en1 est un entier sans signe sur 1 octet // transfère l'entier dans le tableau // les éléments du tableau sont inversés // le tableau est retransféré dans l'entier SI Dimension(paramEntier2ou4) = 4 ALORS en4 = paramEntier2ou4 Transfer(&tbOctet, &en4, 4) en1 = tbOctet[1]; tbOctet[1] = tbOctet[4] tbOctet[4] = en1 en1 = tbOctet[2] tbOctet[2]=tbOctet[3] tbOctet[3]=en1 Transfer(&en4, &tbOctet, 4) RENVOYER en4 SINON en2 = paramEntier2ou4 Transfer(&tbOctet, &en2, 2) en1 = tbOctet[1]; tbOctet[1] = tbOctet[2] tbOctet[2] = en1 Transfer(&en2, &tbOctet, 2) RENVOYER en2 FIN
Ce site en anglais permet d'aborder le format EXIF de manière très pratique avec des exemples. Il contient également des liens vers d'autres sites de référence.
Description en anglais très complète de tous les marqueurs (Tag) utilisés dans le format EXIF.
Document PDF décrivant le format Exif 2.2
Design rule for Camera File system (DCF)
Exchangeable image file format for Digital Still Camera : Exif