Windows Phone – Modification des tags Exif d’un JPEG

3 minutes read

Nous avons vu dans l’article précédent comment compresser une image JPEG sans perdre ses tags. Dans cet article nous allons voir comment modifier ces mêmes tags pour par exemple ajouter des informations de géolocalisation dans l’image.

Tout d’abord il va falloir vous munir d’une bibliothèque permettant d’éditer les tags Exif sur le téléphone. En effet il n’existe pas d’API permettant de le faire nativement sur Windows Phone. A ma connaissance il n’existait aucune bibliothèque gratuite qui permettait de le faire, aussi c’est pourquoi j’ai décidé de porter la bibliothèque Exif Library de Ozgur Ozcitak dont vous pouvez trouver le code source original ici : http://www.codeproject.com/Articles/43665/ExifLibrary-for-NET La version Windows Phone a été publiée récemment sur CodePlex en accord avec l’auteur original. Vous pouvez la télécharger ici : http://wpexiflib.codeplex.com/ Il est important de noter que cette bibliothèque marche pour Windows Phone 7.5 et dans Windows Phone 8 moyennant une migration du projet. A l’heure où ces lignes sont écrites il n’existe pas de package Nuget pour cette bibliothèque mais ce n’est qu’une question de temps. Vous devrez donc soit l’ajouter à votre code source soit la compiler à part et ajouter la dll.

Pour cet article nous allons reprendre le code source de l’article précédent et le modifier un peu pour éditer les tags de géolocalisation. Puisque l’on va travailler avec des coordonnées GPS voici une méthode permettant de convertir des coordonnées sous forme flottante en coordonnées de type degree/minute/secondes

private static void ConvertToDegreeMinutesSeconds(double source, out uint degree, out uint minutes, out double seconds)
{
    degree = (uint)source;
    minutes = (uint)((source - degree) * 60);
    seconds = (((source - degree) * 60) - minutes) * 60;
}

Et maintenant voici le code écrivant les coordonnées GPS dans l’image :

var exifFile = ExifFile.Read(filePath);

if (!exifFile.Properties.ContainsKey(ExifTag.GPSVersionID))
{
    exifFile.Properties.Add(ExifTag.GPSVersionID, new ExifByteArray(ExifTag.GPSVersionID, new[]
        {
            (byte)0x02,
            (byte)0x02,
            (byte)0x00,
            (byte)0x00
        }));
}

uint degree;
uint minutes;
double seconds;
ConvertToDegreeMinutesSeconds(Math.Abs(currentLocation.Latitude), out degree, out minutes, out seconds);
var gpsLatitude = new GPSLatitudeLongitude(
    ExifTag.GPSLatitude,
    new[]
        {
            new MathEx.UFraction32(degree),
            new MathEx.UFraction32(minutes),
            new MathEx.UFraction32(seconds)
        });
ConvertToDegreeMinutesSeconds(Math.Abs(currentLocation.Longitude), out degree, out minutes, out seconds);
var gpsLongitude = new GPSLatitudeLongitude(
    ExifTag.GPSLongitude,
    new[]
        {
            new MathEx.UFraction32(degree),
            new MathEx.UFraction32(minutes),
            new MathEx.UFraction32(seconds)
        });

if (!exifFile.Properties.ContainsKey(ExifTag.GPSLatitude))
    exifFile.Properties.Add(ExifTag.GPSLatitude, gpsLatitude);
else
    exifFile.Properties[ExifTag.GPSLatitude] = gpsLatitude;

if (!exifFile.Properties.ContainsKey(ExifTag.GPSLongitude))
    exifFile.Properties.Add(ExifTag.GPSLongitude, gpsLongitude);
else
    exifFile.Properties[ExifTag.GPSLongitude] = gpsLongitude;

if (!exifFile.Properties.ContainsKey(ExifTag.GPSLatitudeRef))
    exifFile.Properties.Add(ExifTag.GPSLatitudeRef, new ExifAscii(ExifTag.GPSLatitudeRef, currentLocation.Latitude < 0 ? "S" : "N"));
else
    exifFile.Properties[ExifTag.GPSLatitudeRef] = new ExifAscii(ExifTag.GPSLatitudeRef, currentLocation.Latitude < 0 ? "S" : "N");

if (!exifFile.Properties.ContainsKey(ExifTag.GPSLongitudeRef))
    exifFile.Properties.Add(ExifTag.GPSLongitudeRef, new ExifAscii(ExifTag.GPSLongitudeRef, currentLocation.Longitude < 0 ? "W" : "E"));
else
    exifFile.Properties[ExifTag.GPSLongitudeRef] = new ExifAscii(ExifTag.GPSLongitudeRef, currentLocation.Longitude < 0 ? "W" : "E");

exifFile.Save(filePath, true);

Le code complet du scénario qui permet de compresser une image et d’ajouter les coordonnées GPS est donc le suivant :

private static async Task HandleTakenPhoto(Stream photoStream)
{
    string filePath;

    #region Compression
    using (var exifMs = new MemoryStream())
    {
        // Extraction des Exif
        ExtractExifStream(photoStream, exifMs);

        // On revient au début de l'image
        photoStream.Seek(0, SeekOrigin.Begin);

        // Création du stream qui servira à compresser les images
        using (var ms = new MemoryStream())
        {
            var bi = new BitmapImage();
            bi.SetSource(photoStream);

            var wb = new WriteableBitmap(bi);

            // Compression de l'image à 40%
            wb.SaveJpeg(ms, wb.PixelWidth, wb.PixelHeight, 0, 40);
            ms.Seek(0, SeekOrigin.Begin);

            using (var headerMs = new MemoryStream())
            {
                // On passe les header du fichier
                ExtractExifStream(ms, headerMs);

                // On recopie le contenu de l'image sans les header
                await exifMs.WriteAsync(ms.ToArray(), (int)headerMs.Length, (int)(ms.Length - headerMs.Length));
            }
        }

        // On revient au début de l'image
        exifMs.Seek(0, SeekOrigin.Begin);

        const string filename = "YourFileName.jpg";
        // Création de l'image
        var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);

        // Ecriture du Stream sur le disque.
        using (var stream = await file.OpenStreamForWriteAsync())
        {
            await exifMs.CopyToAsync(stream);
        }

        filePath = file.Path;
    }
    #endregion

    #region Recupération de la position courante
    var locator = new Geolocator();
    var position = await locator.GetGeopositionAsync();
    var currentLocation = position.Coordinate;
    #endregion
   
    #region Ecriture des Exif
    var exifFile = ExifFile.Read(filePath);

    if (!exifFile.Properties.ContainsKey(ExifTag.GPSVersionID))
    {
        exifFile.Properties.Add(ExifTag.GPSVersionID, new ExifByteArray(ExifTag.GPSVersionID, new[]
            {
                (byte)0x02,
                (byte)0x02,
                (byte)0x00,
                (byte)0x00
            }));
    }

    uint degree;
    uint minutes;
    double seconds;
    ConvertToDegreeMinutesSeconds(Math.Abs(currentLocation.Latitude), out degree, out minutes, out seconds);
    var gpsLatitude = new GPSLatitudeLongitude(
        ExifTag.GPSLatitude,
        new[]
            {
                new MathEx.UFraction32(degree),
                new MathEx.UFraction32(minutes),
                new MathEx.UFraction32(seconds)
            });
    ConvertToDegreeMinutesSeconds(Math.Abs(currentLocation.Longitude), out degree, out minutes, out seconds);
    var gpsLongitude = new GPSLatitudeLongitude(
        ExifTag.GPSLongitude,
        new[]
            {
                new MathEx.UFraction32(degree),
                new MathEx.UFraction32(minutes),
                new MathEx.UFraction32(seconds)
            });

    if (!exifFile.Properties.ContainsKey(ExifTag.GPSLatitude))
        exifFile.Properties.Add(ExifTag.GPSLatitude, gpsLatitude);
    else
        exifFile.Properties[ExifTag.GPSLatitude] = gpsLatitude;

    if (!exifFile.Properties.ContainsKey(ExifTag.GPSLongitude))
        exifFile.Properties.Add(ExifTag.GPSLongitude, gpsLongitude);
    else
        exifFile.Properties[ExifTag.GPSLongitude] = gpsLongitude;

    if (!exifFile.Properties.ContainsKey(ExifTag.GPSLatitudeRef))
        exifFile.Properties.Add(ExifTag.GPSLatitudeRef, new ExifAscii(ExifTag.GPSLatitudeRef, currentLocation.Latitude < 0 ? "S" : "N"));
    else
        exifFile.Properties[ExifTag.GPSLatitudeRef] = new ExifAscii(ExifTag.GPSLatitudeRef, currentLocation.Latitude < 0 ? "S" : "N");

    if (!exifFile.Properties.ContainsKey(ExifTag.GPSLongitudeRef))
        exifFile.Properties.Add(ExifTag.GPSLongitudeRef, new ExifAscii(ExifTag.GPSLongitudeRef, currentLocation.Longitude < 0 ? "W" : "E"));
    else
        exifFile.Properties[ExifTag.GPSLongitudeRef] = new ExifAscii(ExifTag.GPSLongitudeRef, currentLocation.Longitude < 0 ? "W" : "E");

    exifFile.Save(filePath, true);
    #endregion
}

J’espère que ce code vous sera aussi utile qu’il l’a été pour moi. N’hésitez pas à remercier Ozgur Ozcitak pour son travail original sans lequel il aurait été nettement plus long et compliqué de coder ce scénario.

Updated:

Leave a Comment