Loupe

#Windows 8.1 Binding d’image depuis un texte en base 64

Cette semaine, j’ai du afficher un certain nombre d’images dans le cadre d’un projet, et celles-ci étaient fournies sous la forme d’une chaine de caractères en base 64. Nous allons voir étape par étape comment procéder à cela.

Qu’est-ce qu’un texte en base 64?

Pour ceux qui l’ignorent, base 64 est un mécanisme qui va permettre de représenter des données binaires en caractères imprimables.

Concrètement, chaque caractère va être l’équivalent de 6 bits. Voici l’alphabet qui est utilisé pour ce processus :

image

Ainsi, nous allons avoir une chaine qui va ressembler à : “/9j/4AAQSkZJRgABAgEBLAEsAAD/4QzqRXhpZgAATU0AKgAAAAgADgEPAAIAAAASAAAAt…”.

Je vous invite vivement à consulter cet article pour plus d’information sur le concept du base 64.

 

Notre application

Assez de théorie, passons à la pratique et créons une application Windows 8.1 avec le pattern MVVM. Celle-ci aura pour but d’afficher des… chats !

   public class Cat
    {
        public string Name { get; set; }
        public string Base64Image { get; set; }
    }

Je suppose ici que vous avez déjà vos données et donc un string en base 64.

La première chose qui nous vient à l’esprit dans cette situation, c’est d’utiliser un Converter, simple et efficace. Néanmoins, dans notre cas, nous devons réaliser certaines opérations asynchrones, ce qui nous offre plusieurs possibilités. Parmi celles-ci, nous pouvons créer une implémentation asynchrone de l’interface IValueConverter, ou bien nous orienter vers une Attached Property, ce que nous allons faire.

 

“The” Attached Property

La classe Base64Behavior va contenir une Attached Property qui se nomme Base64String.

  public class Base64Behavior
    {
        public static readonly DependencyProperty Base64StringProperty = DependencyProperty.RegisterAttached(
            "Base64String", typeof (string), typeof (Base64Behavior), new PropertyMetadata(default(string)));

        public static void SetBase64String(DependencyObject element, string value)
        {
            element.SetValue(Base64StringProperty, value);
        }

        public static string GetBase64String(DependencyObject element)
        {
            return (string) element.GetValue(Base64StringProperty);
        }
    }

Une fois notre attached property créée, on va tout de suite rajouter une méthode de callback sur le changement de valeur de notre propriété.

public static readonly DependencyProperty Base64StringProperty = DependencyProperty.RegisterAttached(
           "Base64String", typeof(string), typeof(Base64Behavior), new PropertyMetadata(default(string), Base64String_OnChanged));

Ensuite, on créé donc notre méthode Base64String_OnChanged, qui aura la signature suivante :

private static async void Base64String_OnChanged(DependencyObject dependencyObject,
    DependencyPropertyChangedEventArgs args)
{
}

Enfin, son contenu va être composé de deux parties, le passage des bytes en un objet permettant la construction de l’image (c’est à dire la construction de la source de l’image), et l’assignation de cette source elle-même au contrôle Image.

 

De base64String à une source d’image

var base64String = args.NewValue.ToString();
var imras = new InMemoryRandomAccessStream();
byte[] bytes = Convert.FromBase64String(base64String);

On récupère dans un premier lieu notre string, on créé notre stream et puis, à l’aide de la méthode System.Convert.FromBase64String, on convertit notre string en tableau de bytes.

DataWriter writer = new DataWriter(imras.GetOutputStreamAt(0));
writer.WriteBytes(bytes);
await writer.StoreAsync();

Ensuite, à l’aide d’un DataWriter, on remplit notre stream. Une alternative, qui évite la copie des bytes effectué par le DataWriter est de se passer du DataWriter et de convertir notre tableau de byte en buffer (interface IBuffer)  directement grâce à la méthode d’extension AsBuffer.

await imras.WriteAsync(bytes.AsBuffer());
imras.Seek(0);

On pensera juste à replacer le curseur au début pour l’opération suivante.

Notre source est fin prête, il va falloir maintenant l’associer à notre contrôle Image.

Setter la source de l’image

Nous allons utiliser un BitmapImage. Cependant, il possède la particularité de devoir être créé sur le thread UI. Ce qui nous amène au code ci-dessous.

var imageControl = dependencyObject as Image;
if (imageControl != null)
{
    Action imageAction = async () =>
    {
        var bitmapImage = new BitmapImage();
        await bitmapImage.SetSourceAsync(imras);
        imageControl.Source = bitmapImage;

    };

    if (Window.Current.Dispatcher.HasThreadAccess)
    {
        imageAction();
    }
    else
    {
        Window.Current.Dispatcher.RunIdleAsync(handlerArgs => imageAction());
    }
}

Nous allons devoir exécuter l’imageAction, celle-ci instancie un nouveau BitmapImage à qui on spécifie la source précédemment construite (notre stream). Enfin, on peut fournir le bitmapImage en source à notre Image Control.

C’est maintenant que les choses deviennent intéressantes, à l’aide du booléen HasThreadAccess, on va vérifier si notre code actuel est exécuté sur le thread UI.

Si la condition est vraie, on exécute tout simplement l’action dans le thread actuel, étant donné que nous sommes déja sur le thread UI. Sinon, on doit lancer l’action nous même sur le thread UI grâce à la méthode RunIdleAsync. Elle va tout simplement permettre de planifier l’exécution de l’action fournie (notre imageAction, dans notre cas) avec une priorité basse sur le thread UI, depuis un worker thread. Ce qui va tout simplement permettre d’éviter le freeze de l’UI à tout va.

 

Comment on l’utilise, notre attached property ?

C’est la partie la plus simple, voici la syntaxe à utiliser pour le binding.

<Image behaviors:Base64Behavior.Base64String="{Binding Base64Image}" />

Je vous épargne tout le reste du xaml, vu que ce n’est pas l’objectif de ce billet, mais voici le résultat final !

cats

Conclusion

On peut constater qu’au final il n’y a aucune logique complexe pour nous, étant donné que nous faisons abstraction de toute la logique d’encodage base 64. Il suffit d’assimiler le concept des attached properties et le tour est joué.

Libre à vous de recréer cette application et d’y ajouter des fonctionnalités supplémentaires, comme un SemanticZoom par exemple… !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus