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

2 minute read

Warning : This is content is getting old and may be out of date

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.

Comments