Injecter une image dans un fichier XML ou XAML et la récupérer.

Publié le par Jérémy JANISZEWSKI

INTRODUCTION

Il arrive parfois que nous voulions sauvegarder notre image dans un fichier XML ou XAML. Le problème qui se pose dans notre cas, c'est l'image n'est pas stocké en dur dans le fichier XML ou XAML mais nous avons juste accès au chemin absolu de l'image. C'est un énorme inconvénient, car le poste qui recevra ce fichier XML ou XAML ne pourra afficher l'image car il n'existe pas sur le dit poste. Nous allons rémédier à cela.

 

<Image>C:\Users\UserName\Images\Sample.png</Image>

 

CONVERSION D'UN CHEMIN ABSOLU EN DONNEES D'IMAGE

Le problème qui nous occupe est de "convertir" notre chemin absolu en une série de caractères qui correspond à l'image qui doit être affiché.

Pour cela, il nous suffit simplement de lire le fichier image et de récupérer ces informations dans un objet String. Cela se fait très facilement avec cette classe.

 
 sealed public class FileToBase64Converter { static private FileToBase64Converter _default; static public FileToBase64Converter Default { get { return _default; } } static FileToBase64Converter() { _default = new FileToBase64Converter(); } private FileToBase64Converter() { } public string Convert(string value) { byte[] result = null;  
 
  ExceptionHelper.RaiseNullOrEmptyException(value); ExceptionHelper.RaiseFileNotFoundException(value); 
 using (FileStream fs = new FileStream(value, FileMode.Open, FileAccess.Read)) { result = new byte[fs.Length]; fs.Read(result, 0, (int)fs.Length); fs.Close(); } return System.Convert.ToBase64String(result); } } 

 Remarquez que tout ce passe dans la méthode Convert. Nous ouvrons notre fichier dans un flux et nous récupérons un tableau de Byte définissant les données de l'image. Une fois cela fait, nous encodons ce tableau de Byte en un objet String de base 64 et nous le renvoyons. Les formats XML et XAML sont donc près pour l'injection d'une image.

RECUPERER UNE IMAGE A PARTIR DE SES DONNEES

Lorsque vous lisez un fichier XML qui contient du code pour une image, il faut pouvoir récupérer cette image.

Il suffit simplement de créer un objet BitmapImage et d'utiliser la propriété StreamSource. Cette propriété est de type Stream, donc nous pouvons utiliser un objet MemoryStream qui contiendra notre objet String.

Le code permettant de faire cela s'écrit comme ceci :

 BitmapImage bi = new BitmapImage(); bi.BeginInit(); bi.StreamSource = new MemoryStream(Convert.FromBase64String(value)); bi.EndInit(); bi.Freeze(); 

 Donc ici nous créons notre BitmapImage et nous appelons la méthode BeginInit. En effet, nous ne pouvons utiliser la propriété StreamSource que si elle se trouve entre le couple BeginInit / EndInit.

Ensuite nous définissons notre StreamSource comme étant un objet MemoryStream qui contient en paramètre en tableau de Byte. Donc il nous faut convertir notre objet String de base 64 en un tableau de Byte. Cela se fait grâce à la méthode FromBase64String.

Enfin, nous appelons la méthode EndInit et nous appelons la méthode Freeze pour permettre à la bitmap d'être utilisable dans un contexte multi-thread.

Ensuite il suffit simplement de créer un objet Image et de placer sa propriété Source à bi. Et boum, l'image se dessine parfaitement. 

Pour le format XAML, nous allons effectuer le même principe à ceci près qu'un fichier XAML est lu par un FlowDocument. Or cette classe ne permet pas de créer une image en vue de son intégration dans le fichier XAML. Cependant la classe BlockUIContainer (hérité de la classe Block qu'utilise abusivement la classe FlowDocument) n'est pas scellée. Nous allons donc pouvoir créer notre propre classe permettant l'ajout et la récupération d'une image dans un fichier XAML.

La classe au complet est écrite ainsi :

 /// <summary> /// Represents an inlined image. /// </summary> [ContentProperty("Base64")] sealed public class InlineImage : BlockUIContainer {  #region Dependency Properties /// <summary> /// /// </summary> static public readonly DependencyProperty WidthProperty; /// <summary> /// /// </summary> static public readonly DependencyProperty HeightProperty; /// <summary> /// /// </summary> static public readonly DependencyProperty StretchProperty; /// <summary> /// /// </summary> static public readonly DependencyProperty StretchDirectionProperty; /// <summary> /// /// </summary> static public readonly DependencyProperty HorizontalAlignmentProperty; /// <summary> /// /// </summary> static public readonly DependencyProperty VerticalAlignmentProperty; /// <summary> /// /// </summary> static public readonly DependencyProperty Base64SourceProperty;  #endregion Dependency Properties  #region Properties /// <summary> /// /// </summary> public double Width { get { return (double)GetValue(WidthProperty); } set { SetValue(WidthProperty, value); } } /// <summary> /// /// </summary> public double Height { get { return (double)GetValue(HeightProperty); } set { SetValue(HeightProperty, value); } } /// <summary> /// /// </summary> public Stretch Stretch { get { return (Stretch)GetValue(StretchProperty); } set { SetValue(StretchProperty, value); } } /// <summary> /// /// </summary> public StretchDirection StretchDirection { get { return (StretchDirection)GetValue(StretchDirectionProperty); } set { SetValue(StretchDirectionProperty, value); } } /// <summary> /// /// </summary> public HorizontalAlignment HorizontalAlignment { get { return (HorizontalAlignment)GetValue(HorizontalAlignmentProperty); } set { SetValue(HorizontalAlignmentProperty, value); } } /// <summary> /// /// </summary> public VerticalAlignment VerticalAlignment { get { return (VerticalAlignment)GetValue(VerticalAlignmentProperty); } set { SetValue(VerticalAlignmentProperty, value); } } /// <summary> /// /// </summary> public string Base64Source { get { return (string)GetValue(Base64SourceProperty); } set { SetValue(Base64SourceProperty, value); } }  #endregion Properties  #region Constructor /// <summary> /// Register each dependency property. /// </summary> static InlineImage() { WidthProperty = DependencyProperty.Register("Width", typeof(double), typeof(InlineImage), new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnSizeChanged))); HeightProperty = DependencyProperty.Register("Height", typeof(double), typeof(InlineImage), new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnSizeChanged))); StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(InlineImage), new PropertyMetadata(Stretch.None, new PropertyChangedCallback(OnStretchChanged))); StretchDirectionProperty = DependencyProperty.Register("StretchDirection", typeof(StretchDirection), typeof(InlineImage), new PropertyMetadata(StretchDirection.Both, new PropertyChangedCallback(OnStretchDirectionChanged))); HorizontalAlignmentProperty = DependencyProperty.Register( "HorizontalAlignment", typeof(HorizontalAlignment), typeof(InlineImage), new PropertyMetadata( HorizontalAlignment.Left, new PropertyChangedCallback(OnAlignmentChanged))); VerticalAlignmentProperty = DependencyProperty.Register( "VerticalAlignment", typeof(VerticalAlignment), typeof(InlineImage), new PropertyMetadata( VerticalAlignment.Top, new PropertyChangedCallback(OnAlignmentChanged))); Base64SourceProperty = DependencyProperty.Register( "Base64Source", typeof(string), typeof(InlineImage), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnBase64SourceChanged))); }  #endregion Constructor  #region Methods /// <summary> /// /// </summary> /// <param name="o"></param> /// <param name="e"></param> static private void OnSizeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { InlineImage ii = o as InlineImage; if (ii != null) ii.OnSizeChanged(e.Property, (double)e.OldValue, (double)e.NewValue); } /// <summary> /// /// </summary> /// <param name="oldValue"></param> /// <param name="newValue"></param> private void OnSizeChanged(DependencyProperty dp, double oldValue, double newValue) { Image c = Child as Image; if (c != null) { if (dp == WidthProperty) { if (oldValue == newValue) return; if (!double.IsNaN(newValue)) c.Width = newValue; } else if (dp == HeightProperty) { if (oldValue == newValue) return; if (!double.IsNaN(newValue)) c.Height = newValue; } } } /// <summary> /// /// </summary> /// <param name="o"></param> /// <param name="e"></param> static private void OnStretchChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { InlineImage ii = o as InlineImage; if (ii != null) ii.OnStretchChanged((Stretch)e.OldValue, (Stretch)e.NewValue); } /// <summary> /// /// </summary> /// <param name="oldValue"></param> /// <param name="newValue"></param> private void OnStretchChanged(Stretch oldValue, Stretch newValue) { Image c = Child as Image; if (c != null) { if (oldValue == newValue) return; c.Stretch = newValue; } } /// <summary> /// /// </summary> /// <param name="o"></param> /// <param name="e"></param> static private void OnStretchDirectionChanged( DependencyObject o, DependencyPropertyChangedEventArgs e) { InlineImage ii = o as InlineImage; if (ii != null) ii.OnStretchDirectionChanged( (StretchDirection)e.OldValue, (StretchDirection)e.NewValue); } /// <summary> /// /// </summary> /// <param name="oldValue"></param> /// <param name="newValue"></param> private void OnStretchDirectionChanged( StretchDirection oldValue, StretchDirection newValue) { Image c = Child as Image; if (c != null) { if (oldValue == newValue) return; c.StretchDirection = newValue; } } /// <summary> /// /// </summary> /// <param name="o"></param> /// <param name="e"></param> static private void OnAlignmentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { InlineImage ii = o as InlineImage; if (ii != null) ii.OnAlignmentChanged(e.Property, e.OldValue, e.NewValue); } /// <summary> /// /// </summary> /// <param name="dp"></param> /// <param name="oldValue"></param> /// <param name="newValue"></param> private void OnAlignmentChanged(DependencyProperty dp, object oldValue, object newValue) { Image c = Child as Image; if (c != null) { if (dp == HorizontalAlignmentProperty) { if ((HorizontalAlignment)oldValue == (HorizontalAlignment)newValue) return; c.HorizontalAlignment = (HorizontalAlignment)newValue; } else if (dp == VerticalAlignmentProperty) { if ((VerticalAlignment)oldValue == (VerticalAlignment)newValue) return; c.VerticalAlignment = (VerticalAlignment)newValue; } } } /// <summary> /// /// </summary> /// <param name="o"></param> /// <param name="e"></param> static private void OnBase64SourceChanged( DependencyObject o, DependencyPropertyChangedEventArgs e) { InlineImage ii = o as InlineImage; if (ii != null) ii.OnBase64SourceChanged((string)e.NewValue); } /// <summary> /// /// </summary> /// <param name="p"></param> private void OnBase64SourceChanged(string value) { BitmapImage bi = new BitmapImage(); bi.BeginInit(); bi.StreamSource = new MemoryStream(Convert.FromBase64String(value)); bi.EndInit(); bi.Freeze(); Image result = new Image(); result.StretchDirection = StretchDirection; result.Stretch = Stretch; result.HorizontalAlignment = HorizontalAlignment; result.VerticalAlignment = VerticalAlignment; result.Source = bi; if (!double.IsNaN(Width)) result.Width = Width; if (!double.IsNaN(Height)) result.Height = Height; Child = result; }  #endregion Methods } 

 Cette classe ne sera pas détaillée. Il sert seulement à positionner, dimensionner une image dans le flow document. Le plus important est la méthode OnBase64SourceChanged, qui met en place l'image dans le flow document.

La dernière chose qu'il nous reste à faire est de créer une méthode nous permettant d'ajouter notre objet InlineImage dans le flow document.

 static public void AppendImage(this FlowDocument document, string filename, double renderWidth, double renderHeight, VerticalAlignment va, HorizontalAlignment ha) { ExceptionHelper.RaiseNullException(filename); string base64String = FileToBase64Converter.Default.Convert(filename); InlineImage ii = new InlineImage(); ii.Width = renderWidth; ii.Height = renderHeight; ii.VerticalAlignment = va; ii.HorizontalAlignment = ha; ii.Base64Source = base64String; document.Blocks.Add(ii); } 

Ici, nous convertissons le fichier passé en paramètre en un objet String de base 64. Ensuite nous créons notre objet InlineImage puis nous lui passons nos arguments, comme la taille de rendu, son alignement dans le flow document puis l'objet String en base 64 et enfin nous ajoutons notre objet InlineImage dans la liste des blocks du flow document.

Ainsi, lorsque vous appelerez la méthode XamlWriter.Save(flowdocument, stream) grâce à l'introspection, la classe InlineImage sera serialisée et donc l'image sera contenue dans un objet String en base 64. Et lors du chargement, (XamlReader.Load(Stream)) l'opération inverse se produit et lorsque l'objet String en base 64 est lue, la méthode OnBase64SourceChanged est appelée, et l'image sera affichée.

CONCLUSION

 Ce petit article a montré comment injecter une image dans un fichier XML ou XAML et comment récupérer ces données pour récréer une image. Il y'a juste plus de travail à accomplir pour un fichier XAML car de base l'objet Image ne peut pas être sérialisée comme peut l'être un objet Button.

 N'hésitez pas à laissez des commentaires si les choses ne vous apparaissent pas clair.

 

 Stay tuned,

 

@bientôt sur ce blog.

Publié dans WPF

Pour être informé des derniers articles, inscrivez vous :
Commenter cet article