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 :