I. Introduction▲
L'objectif de cet article est de proposer un exemple simple de lecture/écriture des données Exif d'une image.
L'exemple est créé avec Access mais il est possible d'adapter facilement pour les autres applications Office.
Les données Exif sont des informations stockées dans un fichier Jpeg (ou TIFF).
Elles sont très largement utilisées par les appareils photos numériques pour stocker des informations telles que :
- la date et l'heure du cliché ;
- une miniature de l'image pour prévisualisation rapide
- la vitesse ISO ;
- le modèle de l'appareil ;
- le fabricant de l'appareil ;
- le temps d'exposition ;
- l'utilisation du flash lors de la prise de la photo ;
- ...
Pour lire ces données on utilisera une classe spécifique clGdiPlus qui s'appuie sur la librairie Gdi+ de Microsoft.
Gdi+ renvoie les valeurs brutes selon le type de données mentionné dans la norme.
Par exemple :
Le temps d'exposition est de type RATIO :
On reçoit comme valeur de la propriété un tableau de deux valeurs composant la fraction.
Le type d'appareil est de type ASCII :
On reçoit comme valeur de la propriété une chaîne de caractères.
II. Création du formulaire▲
On crée un formulaire indépendant (sans source de données) dans lequel on place :
- une zone de texte TFichier pour le nom du fichier image ;
- un bouton BFichier pour l'affichage de la boîte de dialogue "Ouvrir un fichier" ;
- un bouton BSauveJPG pour la sauvegarde de l'image au format Jpeg ;
- un contrôle image Image0 pour l'affichage de la miniature ;
- autant de zones de texte qu'il est nécessaire pour afficher les tags Exif ;
Seuls les champs EArtist et EImageDescription seront modifiables.
On modifie les propriétés des autres zones de texte :
Dans l'onglet "Données" :
- Activé : Non
- Verrouillé : Oui
Dans l'onglet "Format" :
- Couleur de fond : 15066597
III. Installation du module clGdiplus▲
Téléchargez le module de classe clGdiplus
Importez le fichier clGdiplus.cls contenu dans l'archive téléchargée.
Vous pouvez soit faire glisser le fichier vers l'explorateur de projet VBA, soit importer le fichier à partir du menu : Fichier => Importer un fichier...
Pour accéder à l'éditeur VBA, vous pouvez utiliser le raccourci clavier ALT-F11.
Ce module de classe VBA utilise la librairie gdiplus.dll de Microsoft.
Si vous utilisez une version de Windows antérieure à XP, télécharger la librairie et placez la dans le même répertoire que le fichier Access.
IV. Déclaration de la classe et initialisation▲
On va écrire notre code dans le module du formulaire. Cliquez sur Affichage --> Code pour ouvrir ce module.
Vérifiez que vous avez l'instruction Option Explicit en haut du module.
Sinon rajoutez le pour imposer la déclaration de toutes les variables, cela évite les étourderies.
Option
Compare Database
Option
Explicit
Pour pouvoir utiliser le module est nécessaire de la déclarer un objet dont le type est le nom sous lequel on a sauvegardé notre module de classe.
Private
oGdi As
clGdiPlus
Première chose à faire : il faut créer une instance de l'objet classe.
(Pour l'instant oGdi a pour valeur Nothing).
On écrit ce code dans l'événement Sur Ouverture du formulaire.
Dans les propriétés du formulaire, définissez [Procédure événementielle] dans l'événement Sur Ouverture.
Cliquez sur les trois petits points [...] pour générer l'événement dans le code.
Private
Sub
Form_Open
(
Cancel As
Integer
)
' Initialisation de la classe de dessin
Set
oGdi =
New
clGdiPlus
End
Sub
Deuxième chose à faire : pensez à libérer la classe dès qu'elle n'est plus utile.
La libération de la classe est importante car elle supprime tous les objets graphiques de la mémoire.
Dans les propriétés du formulaire, définissez [Procédure événementielle] dans l'événement Sur Fermeture.
Cliquez sur les trois petits points [...] pour générer l'événement dans le code.
A l'intérieur de la procédure Form_Close on va libérer la classe : il suffit de lui donner la valeur Nothing si elle n'a pas déjà cette valeur.
Private
Sub
Form_Close
(
)
' Libération de la classe à la fermeture du formulaire
If
Not
oGdi is
Nothing
Then
Set
oGdi =
Nothing
End
Sub
En fait l'objet est normalement libéré automatiquement lorsqu'il est détruit, donc lorsque le formulaire est fermé.
Mais il vaut mieux prendre l'habitude de libérer explicitement les objets pour être sûr.
V. Lecture des données EXIF▲
Pour afficher une boîte de dialogue de sélection de fichier on utilise une fonction spécifique.
Utilisez votre propre fonction si vous en avez déjà une, sinon vous pouvez utiliser la fonction GetFileName dont le code est dans la section Le code complet
Private
Sub
BFichier_Click
(
)
' Boîte de dialogue
TFichier =
GetFileName
(
0
, "Fichier JPEG"
, "JPEG"
, "JPG"
, oGdi.ApplicationPath
)
' Lecture des données Exif après mise à jour du nom de fichier
TFichier_AfterUpdate
End
Sub
On va lire les données Exif après mise à jour du nom de fichier dans la zone de texte TFichier .
On place donc notre code de lecture sur l'événement Après Mise à jour du contrôle TFichier.
- Ouverture du nouveau fichier
- Lecture des données EXIF
On laisse le fichier ouvert tant qu'on ne change pas de fichier.
On peut ainsi modifier une donnée Exif et sauvegarder l'image en cours.
Il faut par contre bien penser à fermer le fichier quand on ne l'utilise plus.
Remarque : La libération de la classe ferme automatiquement un éventuel fichier ouvert.
Il n'est donc pas nécessaire de fermer le fichier à la fermeture du formulaire.
Idem lors de l'ouverture d'un fichier, le précédent est automatiquement fermé.
On déclare d'abord une variable de type Variant pour stocker la valeur brute des données Exif (dans le cas de données complexes).
' Variable pour donnée brute
Dim
lData As
Variant
Puis on ouvre le fichier.
' Ouverture du nouveau fichier
oGdi.LoadFile
TFichier
Après l'appel à la fonction LoadFile, on peut lire les données Exifs et remplir nos zones de texte.
Pour lire une donnée il suffit d'appeler la fonction GetExifData en passant en paramètre le code du tag Exif.
La liste exhaustive des tags est disponible dans la norme Exif.
Les codes des tags les plus courants sont définis dans la classe sous forme de propriété.
Par exemple : TagEquipModel est le code du tag pour lire le type d'appareil photo.
Si un tag n'est pas défini dans l'image la fonction GetExifData renvoie Null.
On va commencer par lire les données simples.
' Taille de l'image
ETaille.Value
=
oGdi.GetExifData
(
TagImageWidth) &
" x "
&
oGdi.GetExifData
(
TagImageHeight)
' Vitesse ISO
EISOSpeedRatings.Value
=
Format
(
oGdi.GetExifData
(
TagISOSpeedRatings), "\I\S\O 000"
)
' Modèle appareil
EEquipModel.Value
=
oGdi.GetExifData
(
TagEquipModel)
' Fabricant
EEquipMake.Value
=
oGdi.GetExifData
(
TagEquipMake)
' Version EXIF
EExifVersion =
oGdi.GetExifData
(
TagExifVersion)
' Description
EImageDescription =
oGdi.GetExifData
(
TagImageDescription)
' Auteur
EArtist =
oGdi.GetExifData
(
TagArtist)
Ensuite on lit les données plus complexes.
Date et heure du cliché :
Le tag DateTimeOriginal renvoie la date et l'heure à laquelle a été prise la photo.
La donnée est un champ de type Date auquel on peut appliquer la fonction Format pour mettre en forme la date.
' Date du cliché
EDateTimeOriginal.Value
=
Format
(
oGdi.GetExifData
(
TagDateTimeOriginal), "d mmmm yyyy"
&
vbCrLf
&
"hh:nn:ss"
)
Miniature :
La miniature intégrée dans le fichier est très pratique.
Elle permet une prévisualisation de l'image sans ouvrir le fichier complet.
On affecte directement la valeur lue par la fonction à la propriété PictureData du contrôle image.
' Miniature
Me.Image0.Picture
=
""
Me.Image0.PictureData
=
oGdi.GetExifData
(
TagThumbnailData)
Temps d'exposition :
Le temps d'exposition est un rationnel, c'est à dire une fraction.
On récupère en fait deux valeurs (sous forme d'un tableau) qui correspondent au numérateur et au dénominateur de la fraction.
Par exemple :
Pour un temps d'exposition de 1/60è de seconde :
- 1ère valeur = 10
- 2ème valeur = 600
- Temps d'exposition = 10/600 = 1/60
Pour un temps d'exposition de 8 secondes :
- 1ère valeur = 800
- 2ème valeur = 100
- Temps d'exposition = 800/100 = 8
' Temps exposition
' On stock d'abord le résultat dans lData
lData =
oGdi.GetExifData
(
TagExposureTime)
' On obtient un tableau de 2 valeurs
If
Not
IsNull
(
lData) Then
If
lData
(
1
) >
lData
(
0
) Then
' Temps inférieur à 1 secondes
EExposureTime.Value
=
"1/"
&
Int
(
lData
(
1
) /
lData
(
0
)) &
" secondes"
Else
' Temps supérieur ou égal à 1 secondes
EExposureTime.Value
=
Int
(
lData
(
0
) /
lData
(
1
)) &
" secondes"
End
If
Else
EExposureTime.Value
=
Null
End
If
Point F :
Cette donnée est également un rationnel.
On calcule la valeur à afficher en divisant le numérateur et le dénominateur.
' Point -F
lData =
oGdi.GetExifData
(
TagFNumber)
If
Not
IsNull
(
lData) Then
EFNumber.Value
=
Format
(
lData
(
0
) /
lData
(
1
), "F0.0"
)
Else
EFNumber.Value
=
Null
End
If
Le flash :
Le flash n'est pas une donnée facile à lire.
La norme précise que la donnée est stockée sous forme d'un nombre qu'il faut transformer en binaire pour ensuite analyser la valeur de chaque bit.
La classe renvoie directement la valeur binaire dans une chaîne de 8 caractères de long.
Le 8ème caractère détermine si le flash s'est déclenché.
Les 4ème et 5ème caractères déterminent le mode de flash défini lors du cliché.
Le 2ème caractère détermine si l'anti-yeux rouges était activé.
(Pour plus d'infos voir la norme Exif).
' Flash
lData =
oGdi.GetExifData
(
TagFlash)
If
Not
IsNull
(
lData) Then
EFlash.Value
=
IIf
(
Mid
(
lData, 8
, 1
) =
"1"
, "Flash déclenché"
, "Flash non déclenché"
)
EFlash.Value
=
EFlash.Value
&
vbCrLf
&
Switch
(
Mid
(
lData, 4
, 2
) =
"00"
, "Mode inconnu"
, _
Mid
(
lData, 4
, 2
) =
"01"
, "Flash forcé"
, _
Mid
(
lData, 4
, 2
) =
"10"
, "Flash désactivé"
, _
Mid
(
lData, 4
, 2
) =
"11"
, "Flash auto"
)
EFlash.Value
=
EFlash.Value
&
vbCrLf
&
Switch
(
Mid
(
lData, 2
, 1
) =
"0"
, "Anti-Yeux rouges désactivé"
, _
Mid
(
lData, 2
, 1
) =
"1"
, "Anti-Yeux rouges activé"
)
Else
EFlash.Value
=
Null
End
If
VI. Modification des données EXIF▲
On va modifier les données Auteur (Artist) et Description (ImageDescription).
Lorsqu'on modifie une donnée Exif, celle-ci n'est pas directement modifiée dans le fichier.
Il faut sauvegarder le fichier pour écrire les modifications.
Les zones EArtist et EImageDescription sont modifiables.
On va modifier la donnée Exif correspondante lors de la mise à jour de ces zones.
La fonction à utiliser est SetExifData, là encore en passant le code du tag en paramètre.
Il faut également donner à la fonction la nouvelle valeur à écrire
Utilisez la propriété Value de la zone de texte pour bien donner la valeur et non l'objet contrôle.
Private
Sub
EArtist_AfterUpdate
(
)
' Mise à jour de l'auteur
oGdi.SetExifData
TagArtist, EArtist.Value
End
Sub
Private
Sub
EImageDescription_AfterUpdate
(
)
' Mise à jour de la description
oGdi.SetExifData
TagImageDescription, EImageDescription.Value
End
Sub
Les modifications ne sont pas apportées sur le fichier directement.
On a avec ce code modifié les données uniquement en mémoire.
VII. Sauvegarde du fichier▲
On va sauvegarder le fichier avec les données Exif modifiées.
Pour éviter de recompresser l'image lors de la sauvegarde, on utilise la fonction SaveJpegLossLess.
L'écrasement du fichier courant n'est pas possible (sauf si vous charger le fichier avec la propriété LoadAndClone = True), il faut sauvegarder sous un autre nom de fichier.
Eventuellement vous pouvez ensuite copier le fichier sous son nom initial puis supprimer la copie.
J'utilise là encore la boîte de dialogue de sélection de fichier.
Le code est bien sûr à placer sur l'événement Sur Click du Bouton BSauveJPG.
Private
Sub
BSauveJPG_Click
(
)
Dim
lFichier As
String
On
Error
GoTo
Gestion_Erreurs
' Récupère un nom de fichier
lFichier =
GetFileName
(
0
, "Fichier JPEG"
, "JPEG"
, "JPG"
, oGdi.ApplicationPath
)
If
lFichier <>
""
Then
' Sauvegarde le fichier
oGdi.SaveJpegLossLess
lFichier
End
If
Gestion_Erreurs
:
If
Err
.Number
<>
0
Then
MsgBox
Err
.Description
End
Sub
VIII. Le code complet▲
Voici le code complet de ce tutoriel :
Option
Compare Database
Option
Explicit
' Déclaration de la classe
Dim
oGdi As
New
clGdiPlus
Private
Sub
BFichier_Click
(
)
' Boîte de dialogue
TFichier =
GetFileName
(
0
, "Fichier JPEG"
, "JPEG"
, "JPG"
, oGdi.ApplicationPath
)
' Lecture des données Exif après mise à jour du nom de fichier
TFichier_AfterUpdate
End
Sub
Private
Sub
BSauveJPG_Click
(
)
Dim
lFichier As
String
On
Error
GoTo
Gestion_Erreurs
' Récupère un nom de fichier
lFichier =
GetFileName
(
0
, "Fichier JPEG"
, "JPEG"
, "JPG"
, oGdi.ApplicationPath
)
If
lFichier <>
""
Then
' Sauvegarde le fichier
oGdi.SaveFile
lFichier
End
If
Gestion_Erreurs
:
If
Err
.Number
<>
0
Then
MsgBox
Err
.Description
End
Sub
Private
Sub
EArtist_AfterUpdate
(
)
' Mise à jour de l'auteur
oGdi.SetExifData
TagArtist, EArtist.Value
End
Sub
Private
Sub
EImageDescription_AfterUpdate
(
)
' Mise à jour de la description
oGdi.SetExifData
TagImageDescription, EImageDescription.Value
End
Sub
Private
Sub
Form_Close
(
)
If
Not
oGdi Is
Nothing
Then
Set
oGdi =
Nothing
End
Sub
Private
Sub
TFichier_AfterUpdate
(
)
' Variable pour donnée brute
Dim
lData As
Variant
' Ouverture du nouveau fichier
oGdi.LoadFile
TFichier
' Taille de l'image
ETaille.Value
=
oGdi.GetExifData
(
TagImageWidth) &
" x "
&
oGdi.GetExifData
(
TagImageHeight)
' Vitesse ISO
EISOSpeedRatings.Value
=
Format
(
oGdi.GetExifData
(
TagISOSpeedRatings), "\I\S\O 000"
)
' Modèle appareil
EEquipModel.Value
=
oGdi.GetExifData
(
TagEquipModel)
' Fabricant
EEquipMake.Value
=
oGdi.GetExifData
(
TagEquipMake)
' Version EXIF
EExifVersion =
oGdi.GetExifData
(
TagExifVersion)
' Description
EImageDescription =
oGdi.GetExifData
(
TagImageDescription)
' Auteur
EArtist =
oGdi.GetExifData
(
TagArtist)
' Date du cliché
EDateTimeOriginal.Value
=
Format
(
oGdi.GetExifData
(
TagDateTimeOriginal), "d mmmm yyyy"
&
vbCrLf
&
"hh:nn:ss"
)
' Miniature
Me.Image0.Picture
=
""
Me.Image0.PictureData
=
oGdi.GetExifData
(
TagThumbnailData)
' Temps exposition
' On stock d'abord le résultat dans lData
lData =
oGdi.GetExifData
(
TagExposureTime)
' On obtient un tableau de 2 valeurs
If
Not
IsNull
(
lData) Then
If
lData
(
1
) >
lData
(
0
) Then
' Temps inférieur à 1 seconde
EExposureTime.Value
=
"1/"
&
Int
(
lData
(
1
) /
lData
(
0
)) &
" seconde"
Else
' Temps supérieur ou égal à 1 seconde
EExposureTime.Value
=
Int
(
lData
(
0
) /
lData
(
1
)) &
" secondes"
End
If
Else
EExposureTime.Value
=
Null
End
If
' Point -F
lData =
oGdi.GetExifData
(
TagFNumber)
If
Not
IsNull
(
lData) Then
EFNumber.Value
=
Format
(
lData
(
0
) /
lData
(
1
), "F0.0"
)
Else
EFNumber.Value
=
Null
End
If
' Flash
lData =
oGdi.GetExifData
(
TagFlash)
If
Not
IsNull
(
lData) Then
EFlash.Value
=
IIf
(
Mid
(
lData, 8
, 1
) =
"1"
, "Flash déclenché"
, "Flash non déclenché"
)
EFlash.Value
=
EFlash.Value
&
vbCrLf
&
Switch
(
Mid
(
lData, 4
, 2
) =
"00"
, "Mode inconnu"
, _
Mid
(
lData, 4
, 2
) =
"01"
, "Flash forcé"
, _
Mid
(
lData, 4
, 2
) =
"10"
, "Flash désactivé"
, _
Mid
(
lData, 4
, 2
) =
"11"
, "Flash auto"
)
EFlash.Value
=
EFlash.Value
&
vbCrLf
&
Switch
(
Mid
(
lData, 2
, 1
) =
"0"
, "Anti-Yeux rouges désactivé"
, _
Mid
(
lData, 2
, 1
) =
"1"
, "Anti-Yeux rouges activé"
)
Else
EFlash.Value
=
Null
End
If
End
Sub
Pour l'ouverture de la boîte de dialogue de sélection d'un fichier, coller ce code dans un module.
Option
Explicit
'Déclaration de l'API
#If VBA7 Then
Private
Declare
PtrSafe Sub
PathStripPath Lib
"shlwapi.dll"
_
Alias "PathStripPathA"
(
ByVal
pszPath As
String
)
Private
Declare
PtrSafe Function
GetOpenFileName Lib
"comdlg32.dll"
_
Alias "GetOpenFileNameA"
(
pOpenfilename As
OPENFILENAME) As
Long
#Else
Private
Declare
Sub
PathStripPath Lib
"shlwapi.dll"
_
Alias "PathStripPathA"
(
ByVal
pszPath As
String
)
Private
Declare
Function
GetOpenFileName Lib
"comdlg32.dll"
_
Alias "GetOpenFileNameA"
(
pOpenfilename As
OPENFILENAME) As
Long
#End If
Private
Type
OPENFILENAME
lStructSize As
Long
#If VBA7 Then
hwndOwner As
LongPtr
hInstance As
LongPtr
#Else
hwndOwner As
Long
hInstance As
Long
#End If
lpstrFilter As
String
lpstrCustomFilter As
String
nMaxCustFilter As
Long
nFilterIndex As
Long
lpstrFile As
String
nMaxFile As
Long
lpstrFileTitle As
String
nMaxFileTitle As
Long
lpstrInitialDir As
String
lpstrTitle As
String
flags As
Long
nFileOffset As
Integer
nFileExtension As
Integer
lpstrDefExt As
String
#If VBA7 Then
lCustData As
LongPtr
lpfnHook As
LongPtr
#Else
lCustData As
Long
lpfnHook As
Long
#End If
lpTemplateName As
String
'#if (_WIN32_WINNT >= 0x0500)
'pvReserved As LongPtr
'dwReserved As Long
'FlagsEx As Long
'#endif // (_WIN32_WINNT >= 0x0500)
End
Type
'Constantes
Private
Const
OFN_HIDEREADONLY =
&
H4
#If VBA7 Then
Public
Function
OuvrirUnFichier
(
handle As
LongPtr, _
Titre As
String
, _
TypeRetour As
Byte, _
Optional
TitreFiltre As
String
, _
Optional
TypeFichier As
String
, _
Optional
RepParDefaut As
String
) As
String
#Else
Public
Function
OuvrirUnFichier
(
handle As
Long
, _
Titre As
String
, _
TypeRetour As
Byte, _
Optional
TitreFiltre As
String
, _
Optional
TypeFichier As
String
, _
Optional
RepParDefaut As
String
) As
String
#End If
Dim
StructFile As
OPENFILENAME
Dim
sFiltre As
String
'Construction du filtre en fonction des arguments spécifiés
If
Len
(
TitreFiltre) >
0
And
Len
(
TypeFichier) >
0
Then
sFiltre =
TitreFiltre &
" ("
&
TypeFichier &
")"
&
Chr
$(
0
) &
"*."
&
TypeFichier &
Chr
$(
0
)
End
If
sFiltre =
sFiltre &
"Tous (*.*)"
&
Chr
$(
0
) &
"*.*"
&
Chr
$(
0
)
'Configuration de la boîte de dialogue
With
StructFile
.lStructSize
=
LenB
(
StructFile) 'Initialisation de la grosseur de la structure
.hwndOwner
=
handle 'Identification du handle de la fenêtre
.lpstrFilter
=
sFiltre 'Application du filtre
.lpstrFile
=
String
$(
254
, vbNullChar
) 'Initialisation du fichier '0' x 254
.nMaxFile
=
254
'Taille maximale du fichier
.lpstrFileTitle
=
String
$(
254
, vbNullChar
) 'Initialisation du nom du fichier '0' x 254
.nMaxFileTitle
=
254
'Taille maximale du nom du fichier
.lpstrTitle
=
Titre 'Titre de la boîte de dialogue
.flags
=
OFN_HIDEREADONLY 'Option de la boite de dialogue
If
((
IsNull
(
RepParDefaut)) Or
(
RepParDefaut =
""
)) Then
RepParDefaut =
CurrentDb.Name
PathStripPath (
RepParDefaut)
.lpstrInitialDir
=
Left
(
CurrentDb.Name
, Len
(
CurrentDb.Name
) -
Len
(
Mid
$(
RepParDefaut, 1
, _
InStr
(
1
, RepParDefaut, vbNullChar
) -
1
)))
Else
: .lpstrInitialDir
=
RepParDefaut
End
If
End
With
If
(
GetOpenFileName
(
StructFile)) Then
'Si un fichier est sélectionné
Select
Case
TypeRetour
Case
1
: OuvrirUnFichier =
Trim
$(
Left
(
StructFile.lpstrFile
, InStr
(
1
, StructFile.lpstrFile
, vbNullChar
) -
1
))
Case
2
: OuvrirUnFichier =
Trim
$(
Left
(
StructFile.lpstrFileTitle
, InStr
(
1
, StructFile.lpstrFileTitle
, vbNullChar
) -
1
))
End
Select
End
If
End
Function
IX. Conclusion▲
N'hésitez pas à lire la norme pour plus de détails ou si vous avez besoin de lire des tags particuliers.
Merci à l'équipe Office de developpez.com pour ses relectures, commentaires et encouragements !