{"id":1358,"date":"2014-12-09T14:59:30","date_gmt":"2014-12-09T13:59:30","guid":{"rendered":"http:\/\/www.aymericlamboley.fr\/blog\/?p=1358"},"modified":"2014-12-09T15:19:23","modified_gmt":"2014-12-09T14:19:23","slug":"taking-screenshot-with-flambe-fighting-the-multi-platforms","status":"publish","type":"post","link":"http:\/\/www.aymericlamboley.fr\/blog\/taking-screenshot-with-flambe-fighting-the-multi-platforms\/","title":{"rendered":"Taking screenshots with Flambe, fighting multi-platforms"},"content":{"rendered":"<p>I enjoy playing with <a href=\"https:\/\/github.com\/aduros\/flambe\" target=\"_blank\">Flambe<\/a>. It&#8217;s the only tech (except <a href=\"http:\/\/openfl.org\/\" target=\"_blank\">OpenFL<\/a>) which enable you to have a SWF, Canvas &#038; WebGL browser game with one code base made in <a href=\"http:\/\/haxe.org\/\" target=\"_blank\">Haxe<\/a> (like OpenFL).<br \/>\nHaving those three targets, you&#8217;re sure to reach your audience at 100%. However one feature, taking an in-game screenshot, may ruin your multi-platforms adventure.<br \/>\n<!--more--><\/p>\n<p>I worked on a <a href=\"http:\/\/www.bandgee.com\/popup\/profil\/modif_avatar\" target=\"_blank\">character customizer<\/a> (you need to create an account to test) for the kids game portal <a href=\"http:\/\/www.bandgee.com\" target=\"_blank\">Bandgee<\/a>. You may change the background, character&#8217;s head, body and legs. There are hundreds of possible combinations. Most of the heads include eyes&#8217; animations made with Flash Pro and <a href=\"http:\/\/threerings.github.io\/flump\/\" target=\"_blank\">Flump<\/a>.<\/p>\n<p>After one topic on <a href=\"https:\/\/groups.google.com\/forum\/#!searchin\/flambe\/screenshot\/flambe\/8OC4FSXflNM\/WFLZpELjMg8J\" target=\"_blank\">Flambe&#8217;s forum<\/a> I thought I was ready to add this feature without pain. But that was a false hope.<\/p>\n<p>After many hours, that&#8217;s why I get : Canvas, Flash, WebGL (from left to right).<br \/>\n<a href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/canvas.png\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/canvas-222x300.png\" alt=\"canvas\" width=\"222\" height=\"300\" class=\"alignleft size-medium wp-image-1360\" srcset=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/canvas-222x300.png 222w, http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/canvas.png 259w\" sizes=\"(max-width: 222px) 100vw, 222px\" \/><\/a><a href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/flash.png\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/flash-222x300.png\" alt=\"flash\" width=\"222\" height=\"300\" class=\"alignmiddle size-medium wp-image-1361\" srcset=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/flash-222x300.png 222w, http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/flash.png 259w\" sizes=\"(max-width: 222px) 100vw, 222px\" \/><\/a><a href=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/webgl.png\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/webgl-222x300.png\" alt=\"webgl\" width=\"222\" height=\"300\" class=\"alignright size-medium wp-image-1362\" srcset=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/webgl-222x300.png 222w, http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2014\/12\/webgl.png 259w\" sizes=\"(max-width: 222px) 100vw, 222px\" \/><\/a><\/p>\n<p>So to sum up:<\/p>\n<ul>\n<li>Canvas: it sounds like I&#8217;ve some black pixel.<\/li>\n<li>Flash: the blue color is missing.<\/li>\n<li>WebGL: the eyes are never rendered (they are animated).<\/li>\n<\/ul>\n<p>Well that could be fixed, but the issue is: they all use the same Haxe code (and I don&#8217;t know what happen under the hood): <\/p>\n<pre lang=\"haxe\">var offsetX = 80;\r\nvar offsetY = 20;\r\n\r\nvar imgWidth = Std.int(Sprite.getBounds(persoEntity).width) - offsetX - 57;\r\nvar imgHeight = Std.int(Sprite.getBounds(persoEntity).height) - offsetY - 40;\r\n\r\nvar texture = System.renderer.createTexture(imgWidth, imgHeight);\r\npersoEntity.get(Perso).showUI(false);\r\nSprite.render(persoEntity, texture.graphics);\r\npersoEntity.get(Perso).showUI(true);\r\n\r\nvar bytes = texture.readPixels(offsetX, offsetY, imgWidth, imgHeight);\r\n\r\nvar urlLoader = new Http(\"script.php\");\r\n\r\nurlLoader.addParameter(\"img\", Base64.encode(bytes));\r\nurlLoader.addParameter(\"imgHeight\", Std.string(imgHeight));\r\nurlLoader.addParameter(\"imgWidth\", Std.string(imgWidth));\r\n\r\nurlLoader.request(true);<\/pre>\n<p>Now the php (thanks St\u00e9phane for all your tests):<\/p>\n<pre lang=\"php\">if (isset($_POST['action']) && !empty($_POST['action'])) {\r\n\r\n    $action = $_POST['action'];\r\n\r\n    switch($action) {\r\n\r\n        case 'img':\r\n        \t\r\n            \/\/the code below write an image but it isn't readable:\r\n            \/\/we have to add png informations on the decoded base64 string.\r\n            \/\/the img is sent as a byte buffer in RGBA order.\r\n\/\/file_put_contents('test22.png', base64_decode($_POST['img']));\r\n\r\n            $encoded = $_POST['img'] ;\r\n            \r\n           \/\/ $encoded = str_replace(' ','+',$encoded);\r\n            \r\n            $decoded = \"\";\r\n            for ($i=0; $i < ceil(strlen($encoded)\/256); $i++)\r\n                $decoded = $decoded . base64_decode(substr($encoded,$i*256,256) , true );\r\n                 \r\n                $data = $decoded ;\r\n            break;\r\n    }\r\n\r\n    $plainText = base64_decode( $_POST['img'] );\r\n\r\n    class ReadPngFile {\r\n        public $data = '';\r\n        public $header = '';\r\n        public $rgba = '';\r\n        public $width = 0;\r\n        public $height = 0;\r\n        public function __construct($imagedata) {\r\n            $this->data = $imagedata;\r\n        }\r\n        \r\n        public function save($dest) {\r\n             \r\n            $this->width = $_POST['imgWidth'];\r\n            $this->height = $_POST['imgHeight'];\r\n             \r\n            \/\/create image\r\n            $img = imagecreatetruecolor($this->width, $this->height);\r\n            \/\/fill by iterating through your raw pixel data\r\n            for($x = 0; $x < $this->width; $x++) {\r\n                for($y = 0; $y < $this->height; $y++) {\r\n                    $pos = ($y * $this->width + $x) * 4;\r\n                    \r\n                    list($red, $green, $blue, $alpha) = array_values(unpack(\"C4\", substr($this->data, $pos, $pos+4)));\r\n                    $alpha = ((int)(substr($alpha - 255, 1))) >> 1;\r\n                    $color = imagecolorallocatealpha ( $img, $red, $green, $blue, $alpha );\r\n                    imagesetpixel($img, $x, $y, $color);\r\n                }\r\n            }\r\n            \r\n            imagepng($img, $dest);\r\n            imagedestroy($img);\r\n        }\r\n    }\r\n    \r\n    header('Content-type: image\/png');\r\n    $contents = $plainText ;\r\n    $gim = new ReadPngFile($contents);\r\n    $gim->save('test.png');\r\n}<\/pre>\n<p>So now we&#8217;re facing an embarassing problem: the php developer can&#8217;t be sure that what he did is 100% correct, and on my side neither. But what is sure is that the php doesn&#8217;t change depending platform. I&#8217;ve the same Haxe code but I don&#8217;t know what it is doing under the hood it might be an issue with the Base64 or most probably with the <em>createTexture<\/em>, <em>render<\/em> or <em>readPixels<\/em> functions.<\/p>\n<p>Here we&#8217;re facing a complicated issue. But let&#8217;s have another angle on this : we&#8217;re fully happy with Canvas performances for this basic avatar maker and Canvas is the only platform available at 100% (we don&#8217;t need a plugin, and works on all iOS &#038; Android platforms unlike WebGL). So just use the Canvas target! Ok but what about the screenshot issue?<br \/>\nWith canvas there is a HTML5 built-in function <a href=\"https:\/\/developer.mozilla.org\/en\/docs\/Web\/API\/HTMLCanvasElement\" target=\"_blank\">toDataURL<\/a>, let&#8217;s use this! I will just need to crop some borders of my screenshots since <em>toDataURL<\/em> save the whole canvas container.<\/p>\n<p>Sometimes we bother with complex stuff while it could be done easily if we keep in mind the main focus: making an avatar customizer reachable by everyone. And in our case WebGL and Flash were just an unnecessary bonus. But the problem is still here if tomorrow I need to make a cross-platform screenshot (SWF, WebGL, Canvas)&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I enjoy playing with Flambe. It&#8217;s the only tech (except OpenFL) which enable you to have a SWF, Canvas &#038; WebGL browser game with one code base made in Haxe (like OpenFL). Having those three targets, you&#8217;re sure to reach your audience at 100%. However one feature, taking an in-game screenshot, may ruin your multi-platforms &hellip; <a href=\"http:\/\/www.aymericlamboley.fr\/blog\/taking-screenshot-with-flambe-fighting-the-multi-platforms\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Taking screenshots with Flambe, fighting multi-platforms<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[180,33,70,167,84,181],"tags":[144,172,34,71,169,89],"_links":{"self":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/1358"}],"collection":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/comments?post=1358"}],"version-history":[{"count":12,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/1358\/revisions"}],"predecessor-version":[{"id":1373,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/1358\/revisions\/1373"}],"wp:attachment":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/media?parent=1358"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/categories?post=1358"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/tags?post=1358"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}