Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Kévin Gosse

Clair, .NET, et précis
Silverlight 3 : WriteableBitmap et DataUri

Rappels sur WriteableBitmap :

Silverlight 3 apporte la classe WriteableBitmap, qui permet d’effectuer le rendu d’un contrôle dans une image. Son utilisation est très simple. On instancie l’objet en lui passant en paramètre le contrôle dont on souhaite effectuer le rendu, puis on assigne le WriteableBitmap à une image pour voir le résultat :

   1: WriteableBitmap wb = new WriteableBitmap(this.btnTest, null);
   2:  
   3: image1.Source = wb;

Là où les choses deviennent plus intéressantes, c’est que depuis la version finale de Silverlight 3, il est possible d’accéder à la valeur des pixels du rendu. On peut ainsi, par exemple, dessiner par dessus le contrôle :

   1: WriteableBitmap wb = new WriteableBitmap(this.btnTest, null);
   2:  
   3: int pixelsPerLine = wb.PixelWidth / wb.PixelHeight;
   4: int horizontalIndex = 0;
   5:  
   6: for (int i = 0; i < wb.PixelHeight; i++)
   7: {
   8:     for (int j = 0; j < pixelsPerLine; j++)
   9:     {
  10:         wb.Pixels[(i * wb.PixelWidth) + j + horizontalIndex] = 255 << 24;
  11:     }
  12:  
  13:     horizontalIndex += pixelsPerLine;
  14: }
  15:  
  16: image1.Source = wb;

Attention toutefois, il n’est possible d’accéder à la valeur des pixels que si le contrôle n’affiche pas des données provenant d’un autre domaine (si vous chargez une image depuis un autre site par exemple, cela ne marchera pas).

Maintenant, voyons comment s’amuser un peu avec cette nouvelle classe.

HTML 5 : les data URI

Les data URI font leur apparition avec HTML5. Cette fonctionnalité est supportée par Internet Explorer 8, donc on peut espérer qu’elle se répande rapidement. Le principe est de placer les données binaires d’une ressource externe directement dans le code HTML. Ainsi, au lieu de référencer une image externe, on peut écrire les données directement dans la page.
Une data URI est composée du type MIME, du texte “base64”, puis des données binaires proprement dites encodées en base 64.

Par exemple :

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABl
BMVEUAAAD/ //+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U g9
C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC

Et en collant les deux ensemble…

Nous avons donc d’un côté une classe permettant d’effectuer le rendu d’un contrôle dans une image, et de l’autre un moyen d’intégrer une image directement dans la source d’une page web. A partir de là, pourquoi ne pas essayer d’effectuer le rendu d’un contrôle directement dans la page web ?

Pour cela, il y a quand même une difficulté, c’est qu’il faut pouvoir convertir la valeur des pixels du rendu dans un format d’image. Pour cela, j’ai donc choisi le bmp, qui est probablement un des formats d’image les moins complexe.

Un coup d’oeil sur Wikipedia nous apprend qu’une image bmp est composée d’un en-tête, puis de la valeur des pixels. Il nous faut donc tout d’abord une fonction pour créer cet en-tête :

   1: private void WriteBitmapHeader(BinaryWriter bw, int width, int height)
   2: {
   3:     // Magic Number
   4:     bw.Write('B');
   5:     bw.Write('M');
   6:  
   7:     // Size of bitmap
   8:     bw.Write((UInt32)0);
   9:  
  10:     // Unused
  11:     bw.Write((UInt32)0);
  12:  
  13:     // Offset where bitmap data starts
  14:     bw.Write((UInt32)54);
  15:  
  16:     // Number of bytes in the header up to this point
  17:     bw.Write((UInt32)40);
  18:  
  19:     // Width of the bitmap (in pixels)
  20:     bw.Write((UInt32)width);
  21:  
  22:     // Height of the bitmap (in pixels)
  23:     bw.Write((UInt32)height);
  24:  
  25:     // Number of color planes being used.
  26:     bw.Write((Byte)0x1);
  27:     bw.Write((Byte)0x0);
  28:  
  29:     // The number of bits/pixel.
  30:     bw.Write((Byte)0x18);
  31:     bw.Write((Byte)0x0);
  32:  
  33:     // No compression used
  34:     bw.Write((UInt32)0);
  35:  
  36:     // The size of the raw BMP data (after this header)
  37:     bw.Write((UInt32)0);
  38:  
  39:     // The horizontal resolution of the image
  40:     bw.Write((UInt32)2835);
  41:  
  42:     // The vertical resolution of the image
  43:     bw.Write((UInt32)2835);
  44:  
  45:     // Number of colors in the palette
  46:     bw.Write((UInt32)0);
  47:  
  48:     // Means all colors are important
  49:     bw.Write((UInt32)0);
  50: }
 

Il s’agit ici de l’application bête et méchante de l’exemple donné par Wikipedia, au détail près que la taille du bitmap n’est pas renseignée (car pénible à calculer et de toutes manières pas utilisée par le navigateur).

Il nous faut ensuite une fonction pour écrire le contenu de l’image. Un petit piège cette fois : les pixels sont énumérés de bas en haut dans une image bmp, alors qu’ils le sont de haut en bas dans le WritableBitmap. Il faut donc penser à faire la transposition. Il faut également ajouter des données à la fin de chaque ligne pour que la taille de la ligne en octets soit un multiple de 4.

   1: private void WriteBitmapContent(BinaryWriter bw, WriteableBitmap bitmap)
   2: {
   3:     int totalPixels = bitmap.PixelHeight * bitmap.PixelWidth;
   4:  
   5:     int paddingSize = (bitmap.PixelWidth * 3) % 4;
   6:  
   7:     for (int y = totalPixels - bitmap.PixelWidth; y >= 0; y -= bitmap.PixelWidth)
   8:     {
   9:         for (int x = 0; x < bitmap.PixelWidth; x++)
  10:         {
  11:             int color = bitmap.Pixels[x + y];
  12:  
  13:             Byte[] pixel = System.BitConverter.GetBytes(color);
  14:  
  15:             for (int i = 0; i < 3; i++)
  16:             {
  17:                 bw.Write(pixel[ i ]);
  18:             }
  19:         }
  20:  
  21:         for (int i = 0; i < paddingSize; i++)
  22:         {
  23:             bw.Write((Byte)0x0);
  24:         }
  25:     }
  26: }

A partir de là, il n’y a plus qu’à coller les morceaux ensemble :

   1: WriteableBitmap wb = new WriteableBitmap(this.btnTest, null);
   2:  
   3: MemoryStream ms = new MemoryStream();
   4: BinaryWriter bw = new BinaryWriter(ms);
   5:  
   6: this.WriteBitmapHeader(bw, wb.PixelWidth, wb.PixelHeight);
   7: this.WriteBitmapContent(bw, wb);
   8:  
   9: bw.Flush();
  10:  
  11: string uri = System.Convert.ToBase64String(ms.ToArray());
  12:  
  13: var img = HtmlPage.Document.CreateElement("img");
  14: img.SetProperty("src", "data:image/bmp;base64," + uri);
  15: img.SetStyleAttribute("position", "absolute");
  16: img.SetStyleAttribute("left", "500px");
  17: img.SetStyleAttribute("top", "500px");
  18:  
  19: HtmlPage.Document.Body.AppendChild(img);

Le rendu n’est pas parfait car le WriteableBitmap gère un canal alpha ce qui n’est pas le cas du bmp. On aurait donc un résultat plus correct en utilisant un format plus complet, comme png.

Si vous voulez voir le rendu final, c’est ici que ça se passe.

Bien sûr, en l’état, tout ceci ne sert pas à grand chose. Mais avec un peu d’imagination, je pense qu’il y a possibilité d’en tirer quelque chose.

A suivre…

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: mercredi 5 août 2009 23:11 par KooKiz
Classé sous : , ,

Commentaires

maitredede a dit :

Intéressant comme article, ça montre une possible alternative au tag canvas du html 5.

Par contre, je pense qu'il existe une limite à la taille des URL en général, donc des URL "data" en particulier. Est-ce que quelqu'un connaitrait cette limite actuelle ?

J'ai trouvé ce site qui apporte un complément d'information : http://www.boutell.com/newfaq/misc/urllength.html

# août 6, 2009 09:11

KooKiz a dit :

La MSDN indique une limite de 32 768 caractères pour Internet Explorer 8. Autant dire qu'il vaut mieux utiliser du png, ça doit partir vite en bmp.

# août 6, 2009 09:32

FREMYCOMPANY a dit :

Amusant. Petite question : C'est assez semblable au RenderTargetBitmap de WPF, ou je me trompe ?

# août 6, 2009 19:01

KooKiz a dit :

A priori, les classes RenderTargetBitmap et WriteableBitmap existent toutes deux en WPF. Il doit donc y avoir une différence, mais je ne saurais pas dire laquelle, faute de les avoir utilisé.

# août 6, 2009 19:27
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [SharePoint] Les sessions TechDays 2012… par Le blog de Patrick [MVP SharePoint] le il y a 4 heures et 33 minutes

- TechDays Paris 2012 : Session pleinière jour 3 par Blog Technique de Romelard Fabrice le 02-09-2012, 11:01

- Mishra Reader : un lecteur RSS très Zune Style en Open Source ! par Cyril Sansus le 02-09-2012, 08:28

- [framework 4] Les Tasks et le Thread UI par Fathi Bellahcene le 02-09-2012, 00:33

- Workflow Foundation 3 a un pied dans la tombe par Blog de Jérémy Jeanson le 02-08-2012, 22:15

- TechDays Paris 2012 : Nouvelles tendances du poste de travail - Bring Your own PC par Blog Technique de Romelard Fabrice le 02-08-2012, 19:42

- TechDays Paris 2012 : System Center Service Manager 2012 Vue d’ensemble par Blog Technique de Romelard Fabrice le 02-08-2012, 17:32

- TechDays Paris 2012 : Pleinière second jour par Blog Technique de Romelard Fabrice le 02-08-2012, 16:23

- TechDays Paris 2012 : Retour d'expérience sur la mise en place d'un Cloud Privé par Blog Technique de Romelard Fabrice le 02-08-2012, 16:04

- TechDays Paris 2012 : Comment SharePoint a sauvé mes TechDays par Blog Technique de Romelard Fabrice le 02-07-2012, 23:59