{"id":54,"date":"2010-10-08T14:00:04","date_gmt":"2010-10-08T13:00:04","guid":{"rendered":"http:\/\/ree7.fr\/blog\/?p=54"},"modified":"2022-08-08T06:47:42","modified_gmt":"2022-08-08T05:47:42","slug":"composition-dimage-rendre-du-xaml-sous-forme-dimage","status":"publish","type":"post","link":"http:\/\/www.ree7.fr\/blog\/2010\/10\/composition-dimage-rendre-du-xaml-sous-forme-dimage\/","title":{"rendered":"Composition d&rsquo;image : rendre du XAML sous forme d&rsquo;image [Update 12\/2011]"},"content":{"rendered":"<p>Voici un morceau de code qui pourront rendre service lors de d\u00e9veloppements Silverlight (<em>3 et +, Windows Phone 7, &#8230;<\/em>) : si vous avez besoin de g\u00e9n\u00e9rer une image dans votre application : \u00e0 partir de Silverlight 3, la classe <strong>WriteableBitmap<\/strong> est \u00e0 votre disposition et permet de cr\u00e9er une image pixel par pixel.<\/p>\n<p>Elle est de transformer un arbre XAML en une image, seule ombre au tableau; cette classe est parfaite pour l&rsquo;affichage \u00e0 l&rsquo;\u00e9cran, en effet le contr\u00f4le <em>Image<\/em> est capable d&rsquo;afficher un <em>WriteableBitmap<\/em> mais pour la sauvegarde c&rsquo;est une autre paire de manche : nativement, Silverlight ne propose pas d&rsquo;encodeur PNG ou JPEG pour exporter ce paquet de pixels.<\/p>\n<p>Le code qui suit vous permettra de profiter de la praticit\u00e9 de Silverlight pour composer une image (\u00e0 vous les superpositions alpha, le texte antialias\u00e9 positionn\u00e9 comme bon vous semble en quelques lignes, les layouts faciles \u00e0 utiliser &#8211; grid, stackpanel, &#8230;) exportable en PNG <strong>non compress\u00e9<\/strong> <em>(attention quand m\u00eame du coup, et en passant merci \u00e0 <\/em><a href=\"http:\/\/blogs.msdn.com\/b\/jstegman\/archive\/2008\/04\/21\/dynamic-image-generation-in-silverlight.aspx\" target=\"_blank\" rel=\"noopener\"><em>Joe Stegman<\/em><\/a><em> pour son PNGEncoder)<\/em>.<\/p>\n<p>Vous pourrez ensuite manipuler ces images comme bon vous semble, car deux formats de sorties sont disponibles, l&rsquo;une en Stream et l&rsquo;autre en Byte[]. Aucun probl\u00e8me pour les\u00a0s\u00e9rialiser\u00a0(contrairement aux BitmapImage) \ud83d\ude42<\/p>\n<p><em>Code and english version of the post available below<\/em><\/p>\n<p><!--more--><\/p>\n<p>Here&rsquo;s a piece of code that could help your Silverlight developments <em>(SL3 and newer, Windows Phone 7, &#8230;)<\/em> : if you need to generate an image in your application, starting with Silverlight 3 the <strong>WriteableBitmap<\/strong> class is available to create an image and interact with it pixel by pixel.<\/p>\n<p><em>WriteableBitmap<\/em> can transform a XAML visual tree into an image (meaning a pixel array); this is perfect for a screen output as the <em>Image <\/em>control knows how to display it. For saving on the other hand, it&rsquo;s a different story : Silverlight simply does not provide a PNG or JPEG encoder to export that pixel array.<\/p>\n<p>The following code will enable you to leverage Silverlight&rsquo;s convenience for composing an image (alpha overlays, antialised text, straightforward containers for your layout, &#8230;), and export it as a PNG image (<strong>uncompressed<\/strong>) &#8211; <em>thanks <a href=\"http:\/\/blogs.msdn.com\/b\/jstegman\/archive\/2008\/04\/21\/dynamic-image-generation-in-silverlight.aspx\" target=\"_blank\" rel=\"noopener\">Joe Stegman<\/a> for your PNGEncoder &#8211;<\/em>.<\/p>\n<p>You will then be able to manipulate those images the way your want, two outputs are available, one as a Stream and the other one as a Byte[]. Unlike the <em>BitmapImage<\/em>, you&rsquo;ll be able to serialize it as a part of your classes :-).<\/p>\n<p>Preview code :<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n\/**********************************************************\r\n * Control2Png.cs\r\n * Renders a Silverlight UIElement as an uncompressed PNG\r\n * stream or byte array. Relies on Joe Stegman's great\r\n * PNGEncoder classes.\r\n *\r\n * Written by : Pierre BELIN\r\n\r\n *\r\n * This program is free software: you can redistribute it and\/or modify\r\n * it under the terms of the Lesser GNU General Public License as published by\r\n * the Free Software Foundation, either version 3 of the License, or\r\n * (at your option) any later version.\r\n *\r\n * This program is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n *\r\n * You should have received a copy of the Lesser GNU General Public License\r\n * along with this program.  If not, see .\r\n *\r\n **********************************************************\/\r\n\r\nusing System;\r\nusing System.IO;\r\nusing System.Windows;\r\nusing System.Windows.Media.Imaging;\r\n\r\nnamespace Ree7.Utils.Imaging\r\n{\r\n    \/\/\/\r\n&lt;summary&gt; \/\/\/ See http:\/\/ree7.fr\/blog\/2010\/10\/composition-dimage-rendre-du-xaml-sous-forme-dimage\/\r\n \/\/\/ &lt;\/summary&gt;\r\n    public class ControlToPng\r\n    {\r\n        public static Stream RenderAsPNGStream(UIElement e)\r\n        {\r\n            try\r\n            {\r\n                WriteableBitmap wb = new WriteableBitmap(e, null);\r\n                EditableImage edit = new EditableImage(wb.PixelWidth, wb.PixelHeight);\r\n\r\n                for (int y = 0; y &lt; wb.PixelHeight; ++y)\r\n                {\r\n                    for (int x = 0; x &lt; wb.PixelWidth; ++x)\r\n                    {\r\n                        byte&amp;#91;&amp;#93; rgba = ExtractRGBAfromPremultipliedARGB(wb.Pixels&amp;#91;wb.PixelWidth * y + x&amp;#93;);\r\n                        edit.SetPixel(x, y, rgba&amp;#91;0&amp;#93;, rgba&amp;#91;1&amp;#93;, rgba&amp;#91;2&amp;#93;, rgba&amp;#91;3&amp;#93;);\r\n                    }\r\n                }\r\n\r\n                return edit.GetStream();\r\n\r\n            }\r\n            catch (Exception)\r\n            {\r\n                return null;\r\n            }\r\n        }\r\n\r\n        public static byte&amp;#91;&amp;#93; RenderAsPNGBytes(UIElement e)\r\n        {\r\n            Stream s = RenderAsPNGStream(e);\r\n\r\n            if (s == null)\r\n                return null;\r\n\r\n            byte&amp;#91;&amp;#93; bytes = ReadFully(s, (int)s.Length);\r\n            return bytes;\r\n        }\r\n\r\n        \/\/\/\r\n&lt;summary&gt; \/\/\/ Convert from premultiplied alpha ARGB to a non-premultiplied RGBA (fix 12\/2011)\r\n \/\/\/ &lt;\/summary&gt;\r\n        \/\/\/\r\n        private static byte&#x5B;] ExtractRGBAfromPremultipliedARGB(int pARGB)\r\n        {\r\n            byte&#x5B;] sourcebytes = new byte&#x5B;4];\r\n            sourcebytes&#x5B;0] = (byte)(pARGB &gt;&gt; 24);\r\n            sourcebytes&#x5B;1] = (byte)((pARGB &amp; 0x00FF0000) &gt;&gt; 16);\r\n            sourcebytes&#x5B;2] = (byte)((pARGB &amp; 0x0000FF00) &gt;&gt; 8);\r\n            sourcebytes&#x5B;3] = (byte)(pARGB &amp; 0x000000FF);\r\n\r\n            if (pARGB == 0) return sourcebytes; \/\/ optimization for images with many transparent pixels\r\n\r\n            byte&#x5B;] destbytes = new byte&#x5B;4];\r\n\r\n            if (sourcebytes&#x5B;0] == 0 || sourcebytes&#x5B;0] == 255)\r\n            {\r\n                destbytes&#x5B;0] = sourcebytes&#x5B;1];\r\n                destbytes&#x5B;1] = sourcebytes&#x5B;2];\r\n                destbytes&#x5B;2] = sourcebytes&#x5B;3];\r\n                destbytes&#x5B;3] = sourcebytes&#x5B;0];\r\n            }\r\n            else\r\n            {\r\n                double factor = 255.0 \/ sourcebytes&#x5B;0];\r\n                double r = sourcebytes&#x5B;1] * factor;\r\n                double g = sourcebytes&#x5B;2] * factor;\r\n                double b = sourcebytes&#x5B;3] * factor;\r\n\r\n                destbytes&#x5B;0] = Convert.ToByte(Math.Min(Byte.MaxValue, r));\r\n                destbytes&#x5B;1] = Convert.ToByte(Math.Min(Byte.MaxValue, g));\r\n                destbytes&#x5B;2] = Convert.ToByte(Math.Min(Byte.MaxValue, b));\r\n                destbytes&#x5B;3] = sourcebytes&#x5B;0];\r\n            }\r\n\r\n            return destbytes;\r\n        }\r\n\r\n        \/\/\/\r\n&lt;summary&gt; \/\/\/ Reads data from a stream until the end is reached. The\r\n \/\/\/ data is returned as a byte array. An IOException is\r\n \/\/\/ thrown if any of the underlying IO calls fail.\r\n \/\/\/ &lt;\/summary&gt;\r\n        \/\/\/The stream to read data from\r\n        \/\/\/The initial buffer length\r\n        private static byte&#x5B;] ReadFully(Stream stream, int initialLength)\r\n        {\r\n            \/\/ If we've been passed an unhelpful initial length, just\r\n            \/\/ use 32K.\r\n            if (initialLength &lt; 1)\r\n            {\r\n                initialLength = 32768;\r\n            }\r\n\r\n            byte&amp;#91;&amp;#93; buffer = new byte&amp;#91;initialLength&amp;#93;;\r\n            int read = 0;\r\n\r\n            int chunk;\r\n            while ((chunk = stream.Read(buffer, read, buffer.Length - read)) &gt; 0)\r\n            {\r\n                read += chunk;\r\n\r\n                \/\/ If we've reached the end of our buffer, check to see if there's\r\n                \/\/ any more information\r\n                if (read == buffer.Length)\r\n                {\r\n                    int nextByte = stream.ReadByte();\r\n\r\n                    \/\/ End of stream? If so, we're done\r\n                    if (nextByte == -1)\r\n                    {\r\n                        return buffer;\r\n                    }\r\n\r\n                    \/\/ Nope. Resize the buffer, put in the byte we've just\r\n                    \/\/ read, and continue\r\n                    byte&#x5B;] newBuffer = new byte&#x5B;buffer.Length * 2];\r\n                    Array.Copy(buffer, newBuffer, buffer.Length);\r\n                    newBuffer&#x5B;read] = (byte)nextByte;\r\n                    buffer = newBuffer;\r\n                    read++;\r\n                }\r\n            }\r\n            \/\/ Buffer is now too big. Shrink it.\r\n            byte&#x5B;] ret = new byte&#x5B;read];\r\n            Array.Copy(buffer, ret, read);\r\n            return ret;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p><strong>Update 01\/12\/2011 :<\/strong> Fixed the error for translucent pixels that were not looking right, I forgot to offset the color values as WritableBitmap gives premultiplied alpha pixel values.<\/p>\n<ul>\n<li>Source code : <a href=\"http:\/\/ree7.fr\/blog\/wp-content\/uploads\/2010\/10\/Control2Png.zip\">Control2Png.zip<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Voici un morceau de code qui pourront rendre service lors de d\u00e9veloppements Silverlight (3 et +, Windows Phone 7, &#8230;) : si vous avez besoin de g\u00e9n\u00e9rer une image dans votre application : \u00e0 partir de Silverlight 3, la classe WriteableBitmap est \u00e0 votre disposition et permet de cr\u00e9er une image pixel par pixel. Elle [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[18],"tags":[31,52,33,29,21,20,32,30],"jetpack_publicize_connections":[],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p19lzH-S","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/posts\/54"}],"collection":[{"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/comments?post=54"}],"version-history":[{"count":1,"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/posts\/54\/revisions"}],"predecessor-version":[{"id":596,"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/posts\/54\/revisions\/596"}],"wp:attachment":[{"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/media?parent=54"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/categories?post=54"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.ree7.fr\/blog\/wp-json\/wp\/v2\/tags?post=54"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}