Taking screenshots with Flambe, fighting multi-platforms


Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /homepages/8/d317303823/htdocs/blog/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /homepages/8/d317303823/htdocs/blog/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

I enjoy playing with Flambe. It’s the only tech (except OpenFL) which enable you to have a SWF, Canvas & WebGL browser game with one code base made in Haxe (like OpenFL).
Having those three targets, you’re sure to reach your audience at 100%. However one feature, taking an in-game screenshot, may ruin your multi-platforms adventure.

I worked on a character customizer (you need to create an account to test) for the kids game portal Bandgee. You may change the background, character’s head, body and legs. There are hundreds of possible combinations. Most of the heads include eyes’ animations made with Flash Pro and Flump.

After one topic on Flambe’s forum I thought I was ready to add this feature without pain. But that was a false hope.

After many hours, that’s why I get : Canvas, Flash, WebGL (from left to right).
canvasflashwebgl

So to sum up:

  • Canvas: it sounds like I’ve some black pixel.
  • Flash: the blue color is missing.
  • WebGL: the eyes are never rendered (they are animated).

Well that could be fixed, but the issue is: they all use the same Haxe code (and I don’t know what happen under the hood):

var offsetX = 80;
var offsetY = 20;
 
var imgWidth = Std.int(Sprite.getBounds(persoEntity).width) - offsetX - 57;
var imgHeight = Std.int(Sprite.getBounds(persoEntity).height) - offsetY - 40;
 
var texture = System.renderer.createTexture(imgWidth, imgHeight);
persoEntity.get(Perso).showUI(false);
Sprite.render(persoEntity, texture.graphics);
persoEntity.get(Perso).showUI(true);
 
var bytes = texture.readPixels(offsetX, offsetY, imgWidth, imgHeight);
 
var urlLoader = new Http("script.php");
 
urlLoader.addParameter("img", Base64.encode(bytes));
urlLoader.addParameter("imgHeight", Std.string(imgHeight));
urlLoader.addParameter("imgWidth", Std.string(imgWidth));
 
urlLoader.request(true);

Now the php (thanks Stéphane for all your tests):

if (isset($_POST['action']) && !empty($_POST['action'])) {
 
    $action = $_POST['action'];
 
    switch($action) {
 
        case 'img':
 
            //the code below write an image but it isn't readable:
            //we have to add png informations on the decoded base64 string.
            //the img is sent as a byte buffer in RGBA order.
//file_put_contents('test22.png', base64_decode($_POST['img']));
 
            $encoded = $_POST['img'] ;
 
           // $encoded = str_replace(' ','+',$encoded);
 
            $decoded = "";
            for ($i=0; $i < ceil(strlen($encoded)/256); $i++)
                $decoded = $decoded . base64_decode(substr($encoded,$i*256,256) , true );
 
                $data = $decoded ;
            break;
    }
 
    $plainText = base64_decode( $_POST['img'] );
 
    class ReadPngFile {
        public $data = '';
        public $header = '';
        public $rgba = '';
        public $width = 0;
        public $height = 0;
        public function __construct($imagedata) {
            $this->data = $imagedata;
        }
 
        public function save($dest) {
 
            $this->width = $_POST['imgWidth'];
            $this->height = $_POST['imgHeight'];
 
            //create image
            $img = imagecreatetruecolor($this->width, $this->height);
            //fill by iterating through your raw pixel data
            for($x = 0; $x < $this->width; $x++) {
                for($y = 0; $y < $this->height; $y++) {
                    $pos = ($y * $this->width + $x) * 4;
 
                    list($red, $green, $blue, $alpha) = array_values(unpack("C4", substr($this->data, $pos, $pos+4)));
                    $alpha = ((int)(substr($alpha - 255, 1))) >> 1;
                    $color = imagecolorallocatealpha ( $img, $red, $green, $blue, $alpha );
                    imagesetpixel($img, $x, $y, $color);
                }
            }
 
            imagepng($img, $dest);
            imagedestroy($img);
        }
    }
 
    header('Content-type: image/png');
    $contents = $plainText ;
    $gim = new ReadPngFile($contents);
    $gim->save('test.png');
}

So now we’re facing an embarassing problem: the php developer can’t be sure that what he did is 100% correct, and on my side neither. But what is sure is that the php doesn’t change depending platform. I’ve the same Haxe code but I don’t know what it is doing under the hood it might be an issue with the Base64 or most probably with the createTexture, render or readPixels functions.

Here we’re facing a complicated issue. But let’s have another angle on this : we’re fully happy with Canvas performances for this basic avatar maker and Canvas is the only platform available at 100% (we don’t need a plugin, and works on all iOS & Android platforms unlike WebGL). So just use the Canvas target! Ok but what about the screenshot issue?
With canvas there is a HTML5 built-in function toDataURL, let’s use this! I will just need to crop some borders of my screenshots since toDataURL save the whole canvas container.

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)…

2 thoughts on “Taking screenshots with Flambe, fighting multi-platforms

  1. Ah this sucks. To spot where the difference is coming from, you could check this:
    1. If you compare the bytes you get from both platforms, are they different?
    2. If those are the same, if you compare the Base64 encoded data, are they different?

    – For the missing eyes, would it help it you wait a frame?

    Maybe also open a issue on the Flambe github.

Leave a Reply

Your email address will not be published. Required fields are marked *