WP8 – Compression d’un JPEG et conservation des tags Exif

2 minutes read

Suite à mes aventures dans le merveilleux monde de la manipulation d’images sous Windows Phone j’ai récemment eu le besoin de compresser un JPEG tout en conservant ses tags Exif. Le problème de cela est que la méthode classique pour compresser un JPEG (décrite dans mon article précédent) fait disparaitre ces tags.

A force de recherches je suis tombé sur cet article montrant comment supprimer les tags Exif et m’en suis servi comme base pour répondre à mon besoin :

http://techmikael.blogspot.co.uk/2009/07/removing-exif-data-continued.html

L’idée derrière la méthode que j’ai utilisé consiste à lire les headers du fichier image source jusqu’à la fin du header Exif et de le recopier au début d’un nouveau fichier image. Ensuite il n’y a plus qu’à écrire l’image compressée à la suite. Tout d’abord voici le code de l’article précédent modifié permettant d’écrire les header du fichier source dans un nouveau Stream :

    
private static void ExtractExifStream(Stream inStream, Stream outStream)
{
    var jpegHeader = new byte[2];
    jpegHeader[0] = (byte)inStream.ReadByte();
    jpegHeader[1] = (byte)inStream.ReadByte();

    if (jpegHeader[0] == 0xff && jpegHeader[1] == 0xd8) // Vérifie si c'est un JPEG
    {
        outStream.WriteByte(jpegHeader[0]);
        outStream.WriteByte(jpegHeader[1]);

        var header = new byte[2];
        header[0] = (byte)inStream.ReadByte();
        header[1] = (byte)inStream.ReadByte();
        outStream.WriteByte(header[0]);
        outStream.WriteByte(header[1]);

        while (header[0] == 0xff && (header[1] >= 0xe0 && header[1] <= 0xef))
        {
            int exifLength = inStream.ReadByte();
            outStream.WriteByte((byte)exifLength);
            exifLength = exifLength << 8;
            exifLength |= inStream.ReadByte();
            outStream.WriteByte((byte)exifLength);

            for (int i = 0; i < exifLength - 2; i++)
            {
                var b = (byte)inStream.ReadByte();
                outStream.WriteByte(b);
            }

            header[0] = (byte)inStream.ReadByte();
            header[1] = (byte)inStream.ReadByte();
            outStream.WriteByte(header[0]);
            outStream.WriteByte(header[1]);
        }
    }
}

Et voici comment utiliser ce code pour compresser l’image source et garder les tags Exif :

private void ChoosePhoto_OnClick(object sender, RoutedEventArgs e)
{
    var cct = new PhotoChooserTask();
    cct.Completed += async (s, a) =>
    {
        if (a.TaskResult == TaskResult.OK)
            await HandleTakenPhoto(a.ChosenPhoto);
    };
    cct.Show();
}

private static async Task HandleTakenPhoto(Stream photoStream)
{
    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);

        // Enregistrer la photo sur le disque ici
        // Voir article précédent
    }
}

Comme d’habitude le code est commenté de façon à expliquer ce que fait chaque étape.

Updated:

Leave a Comment