2

I am having problems extracting the layer contents from a .tmx (Tiled) file. I would like to get the complete uncompressed data in PHP and make a little image of it. Getting the header information like width, height and so on is no problem - SimpleXML is doing its job there. But somehow decompressing of the tile layer is not working.

The data itself is stored as a base64 and gzip encoded string (sth like H4sIAAAAAAAAC+3bORKAIBQEUVzuf2YTTSwEA/gL00EnJvJQsAjcSyk7EU3v+Jn3OI) but I am having problems even getting the base64 decoded code (it just gives me wierd characters and when i reopened the map in tiled and saved it as "base64 uncompressed" the result was just an empty string - not using gzip decompressing of course).

I already searched through the web and saw how the data is exactly compressed (Github article). It seems like i have to use the gzinflate() command instead of all the others (e.g. gzuncompress), but this is also not working for me.

The code i have now is the following:

<?php
  // Get the raw xml data
  $map_xml = new SimpleXML(file_get_contents("map.tmx"));
  $data = $map_xml["layer"][0]["data"]["@content"]; // I would make a loop here
  $content =gzinflate(base64_decode($map_content)); // gives me and error

  var_dump($data); // results in nothing
?>

After some more research I found out that I should use a zlib filter (php.net article). Now I was really confused I don't know what I should pick - I asked google again and got the following: Compressing with Java Decompressing with PHP. According to the answer I have to crop our the header before using the base64 and gzip methods.

Now my questions: Do I have to crop out the header before? If yes, how do I do that? If not, how can I get the uncompressed data then?

I really hope that someone can help me in here!

Community
  • 1
  • 1
spaceemotion
  • 1,404
  • 4
  • 24
  • 32

3 Answers3

4

Php's gzinflate and gzuncompress are, as previously noted, incorrectly named. However, we can take advantage of gzinflate which accepts raw compressed data. The gzip header is 10 bytes long which can be stripped off using substr. Using your example above I tried this:

$base64content = "H4sIAAAAAAAAC+3bORKAIBQEUVzuf2YTTSwEA/gL00EnJvJQsAjcSyk7EU3v+Jn3OI";
$compressed = substr( base64_decode($base64content), 10);
$content = gzinflate($compressed);

This gives you a string representing the raw data. Your TMX layer consists mostly of gid 0, 2, and 3 so you'll only see whitespace if you print it out. To get helpful data, you'll need to call ord on the characters:

$chars = str_split($content);
$values = array();
foreach($chars as $char) {
    $values[] = ord($char);
}
var_dump( implode(',', $values) );  // This gives you the equivalent of saving your TMX file with tile data stored as csv

Hope that helps.

Swarf
  • 76
  • 3
2

Wow, these PHP functions are horribly named. Some background first.

There are three formats you are likely to encounter or be able to produce. They are:

  • Raw deflate, which is data compressed to the deflate format with no header or trailer, defined in RFC 1951.
  • zlib, which is raw deflate data wrapped in a compact zlib header and trailer which consists of a two-byte header and a four-byte Adler-32 check value as the trailer, defined in RFC 1950.
  • gzip, which is raw deflate data wrapped in a gzip header and trailer where the header is at least ten bytes, and can be longer containing a file name, comments, and/or an extra field, and an eight-byte trailer with a four-byte CRC-32 and a the uncompressed length module 2^32. This wrapper is defined in RFC 1952. This is the data you will find in a file with the suffix .gz.

The PHP functions gzdeflate() and gzinflate() create and decode the raw deflate format. The PHP functions gzcompress() and gzuncompress() create and decode the zlib format. None of these functions should have "gz" in the name, since none of them handle the gzip format! This will forever be confusing to PHP coders trying to create or decode gzip-formatted data.

There seem to be (but the documentation is not clear if they are always there) PHP functions gzencode() and gzdecode() which, if I am reading the terse documentation correctly, by default create and decode the gzip format. gzencode() also has an option to produce the zlib format, and I suspect that gzdecode() will attempt to automatically detect the gzip or zlib format and decode accordingly. (That is a capability that is part of the actual zlib library that all of these functions use.)

The documentation for zlib_encode() and zlib_decode() is incomplete (where those pages admit: "This function is currently not documented; only its argument list is available"), so it is difficult to tell what they do. There is an undocumented encoding string parameter for zlib_encode() that presumably would allow you to select one of the three formats, if you knew what to put in the string. There is no encoding parameter for zlib_decode(), so perhaps it tries to auto-detect among the three formats.

Community
  • 1
  • 1
Mark Adler
  • 101,978
  • 13
  • 118
  • 158
  • Sadly I only have PHP 5.3.1 installed - neither zlib_encode() or zlib_decode() exist in that build. also have not found any other implementation of it that i can try out. But your answer really helped me out understanding all the differences between the different methods, thanks a lot! – spaceemotion May 16 '12 at 16:55
  • yeah only the zlib_* functions are not existing – spaceemotion May 16 '12 at 18:44
  • none of them - thats my problem, somehow even base64decode is not working :( – spaceemotion May 16 '12 at 20:31
1

I know this is old now, but I've literally spent all day playing with this code.
It's been really picky about what I do. However, here's a quick function to turn TMX files into an array of IDs for each tile on each layer.

Credits go to the other answerers who helped me piece together where I was going wrong.

<?php

function getLayer($getLayerName = '')
{
    $xml = simplexml_load_file('level.tmx');
    $values = array();
    foreach($xml->layer as $child)
    {
        $name = $child->attributes()->name;

        if(!empty($getLayerName))
            if($name != $getLayerName)
                continue;

        $data = gzinflate(substr(base64_decode(trim($child->data)), 10));
        $chars = str_split($data);
        $i = 0;
        foreach($chars as $char)
        {
            $charID = ord($char);
            if($i % 4 == 0) // I'm only interested in the tile IDs
            {
                $values[(String) $name][] = $charID;
            }
            $i++;
        }
    }
    return $values;
}

print_r(getLayer());
//or you could use getLayer('LayerName') to get a single layer!

?>

On my example 3x3 map, with only one tile image, I get the following:

Array
(
    [floor] => Array
        (
            [0] => 1
            [1] => 1
            [2] => 1
            [3] => 1
            [4] => 1
            [5] => 1
            [6] => 1
            [7] => 1
            [8] => 1
        )

    [layer2] => Array
        (
            [0] => 0
            [1] => 0
            [2] => 1
            [3] => 0
            [4] => 1
            [5] => 0
            [6] => 1
            [7] => 1
            [8] => 0
        )

)

Hopefully this function proves handy for anyone out there who needs it.

Ben Poulson
  • 3,368
  • 2
  • 17
  • 26