Comment redimensionner une image avec WinRT : plusieurs solutions

Tags: IO, WinRT

J'ai participé hier à une session du Tour de France Windows 8 à Bordeaux. Session très sympathique avec de charmants co-développeurs.

J'en ai profité pour m'avancer sur mon projet d'application WinRT et de me pencher plus avant sur la problématique suivante :

J'ai des nombreuses images dans mon application qui sont de taille "importante" (supérieure à 1024*768). Or il se trouve que ces images peuvent être utilisées pour la vignette de mon application. J'ai donc soit le choix de créer "à la main" une version light de mes images, soit ajouter la fonctionnalité à mon application de redimensionnement d'images (pour info, le poids des images affichées dans les vignettes est limitée, 150Ko je crois de mémoire).

La solution que j'ai trouvée était la suivante :

Une solution "pas simple"

La première étape consiste à récupérer la première frame de l'image à partir du fichier :

// imageFilename 
//      C'est le nom du fichier. 
//      Ici, c'est un fichier image que l'on a placé dans notre projet en tant que Contenu
//      (répertoire Assets dans le projet template de MS) 
//      Il se trouve donc dans le InstalledLocation
//      récupération du fichier, StorageFile
// fileName
//      nom du fichier que l'on veut sauvegarder
// width, height
//      largeur et hauteur de l'image finale désirée

var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(imageFilename);
// récupération du flux du fichier
var baseStream = RandomAccessStreamReference.CreateFromFile(file);
// décodeur de l'image à partir du flux ouvert en lecture
var fileDecoder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(await baseStream.OpenReadAsync());
// récupération de la première frame de l'image
var bitmapFrame = await fileDecoder.GetFrameAsync(0);

Ensuite, on récupère les pixels de notre image en spécifiant la taille de l'image finale :

// on veut une image de la taille width*height
var bitmapTransform = new BitmapTransform { ScaledWidth = width, ScaledHeight = height };
// récupération des pixels
var pixelDatas =
    await bitmapFrame.GetPixelDataAsync(
        bitmapFrame.BitmapPixelFormat,
        bitmapFrame.BitmapAlphaMode,
        bitmapTransform,
        ExifOrientationMode.IgnoreExifOrientation,
        ColorManagementMode.DoNotColorManage);

 

Enfin, on crée le fichier final et on écrit les pixels dedans :

// le fichier ou l'on souhaite enregistrer l'image redimensionnée.
// On la place dans le LocalFolder de l'application
var targetFile =
    await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(
        fileName,
        CreationCollisionOption.ReplaceExisting)
        .AsTask<storagefile>();

// ouverture du flux pour écrire dans le fichier
using (var fileStream = await targetFile.OpenAsync(FileAccessMode.ReadWrite))
{
    // l'encodeur de notre nouveau fichier image
    var newBitmapEncoder = 
        await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(
            BitmapEncoder.JpegEncoderId, 
            fileStream);
    // on écrit les pixels dans notre nouveau fichier en respectant les infos d'origine (dpi, alpha, etc.)
    newBitmapEncoder.SetPixelData(
        bitmapFrame.BitmapPixelFormat, 
        bitmapFrame.BitmapAlphaMode, 
        bitmapTransform.ScaledWidth, 
        bitmapTransform.ScaledHeight, 
        bitmapFrame.DpiX, 
        bitmapFrame.DpiY, 
        pixelDatas.DetachPixelData());

    // on s'assure que l'on vide bien tout
    await newBitmapEncoder.FlushAsync();
    await fileStream.FlushAsync();
}

 

Ce n'est pas vraiment simple, mais ca marche.

Une solution plus simple

Montrant ce code à notre cher GO, aka Benoit Laut de Bewise, il me dit : "Mais non, espèce d'abruti, y'a beaucoup plus simple :!".

Bon, OK, il m'a pas dit ca comme ca exactement ;-). Je précise pour ceux qui comprendrait pas le second degré. Il m'a dit très gentillement qu'il pensait qu'il y avait plus simple, notamment avec les méthodes GetThumbnails du framework. Il a donc recherché et quelques temps plus tard, il m'a trouvé le code suivant :

var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(imageFilename);
// on veut une vignette de type image de largeur width et redimensionnée
var thumbnail = await file.GetThumbnailAsync(
    Windows.Storage.FileProperties.ThumbnailMode.PicturesView, 
    width, 
    Windows.Storage.FileProperties.ThumbnailOptions.ResizeThumbnail);

// on crée le fichier cible
var storageFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
// ATTENTION : Ajoutez System.IO qui comprend ces méthodes d'extensions
// le flux ou l'on souhaite écrire
var writer = await storageFile.OpenStreamForWriteAsync(); 
var outputStream = writer.AsOutputStream();

// sauvegarde dans le flux de la vignette
await RandomAccessStream.CopyAndCloseAsync(thumbnail, outputStream);

Il y a quand même beaucoup moins de ligne de code, et ca à l'air plus simple.

 

MAIS

Car souvent avec moi, il y a un MAIS. J'ai donc voulu voir le résultat de ces deux possibilités. Concrètement, on obtient bien une vignette avec la taille désirée (dans le deuxième cas, on ne peut pas spécifier la largeur ET la hauteur, mais ce n'est pas grave dans notre cas). Les deux images sont bien générées :

J'ai bien mes patates redimensionnées et on peut constater que le temps d'exécution est sensiblement le même.

Mais concernant le poids des fichiers, ce n'est pas du tout la même histoire.

  • Le fichier d'origine fait 250Ko.
  • Le ficher avec la méthode complexe fait 36Ko
  • Le fichier avec la méthode simple fait 250Ko !!!

C'est bizarre (et si vous avez une explication, je suis preneur). Mais toujours est-il que je vas donc utiliser la méthode "complexe" dans mon application.

Conclusion

Tout comme dans le framework .NET "normal", pour faire une même opération, on a de multiples possibilités. Mais l'avantage avec le framework normal, c'est que grâce à des décompilateurs, on peut savoir pourquoi. Peux être que sur des images beaucoup plus lourde, la solution complexe est beaucoup plus rapide, peux être qu'avec la version finale, le poid de l'image finale sera optimisée.

Mais comme on n'a pas accès à des décompilateurs pour voir le code qui est réellement exécuté, on est encore dans le flou, et seul des tests permettent de savoir ce qu'il faut utiliser.

 

blog comments powered by Disqus