Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm trying to import a Tiled Map Editor JSON file into my HTML5 canvas game but I'm having trouble with separating out the tileset image into separate images while preserving the transparency.

Currently I'm using a temporary canvas element and the getImageData() and putImageData() functions to grab specific parts of the tileset image and push the separate "tile" image into an array.

I understand that putImageData() renders the Image Data pixel by pixel, thus filling the transparent pixels with the background color of the page, but I can't figure out exactly how to fix this.

You can see the problem here:

enter image description here

Here's the code:

(function() {

var tilesets = [];
var tiles = [];
tiles.push(null);

tmxJSON = {

    map: null,

    load: function(jsonFile) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                // Call function for next step in process
                tmxJSON.parse(xhr.responseText);
            }
        }
        xhr.open("GET", jsonFile, true);
        xhr.send();
    },

    parse: function(json) {
        map = eval("(" + json + ")");

        tmxJSON.loadTilesetImages();

    },

    loadTilesetImages: function() {

        var successCount = 0;
        var errorCount = 0;

        for (var ts = 0; ts < map.tilesets.length; ts++) {

            var image = new Image();
            image.addEventListener("load", function() {
                successCount++;
                if (successCount + errorCount == map.tilesets.length) {
                    tmxJSON.separateTiles();
                }
            });
            image.addEventListener("error", function() {
                errorCount++;
                alert("error loading: " + map.tilesets[ts].image);
            });
            image.src = map.tilesets[ts].image;

            tilesets.push(image);

        }


    },

    separateTiles: function() {

        for (var ts = 0; ts < tilesets.length; ts++) {

            var tileCanvas = document.createElement("canvas");
            var tileContext = tileCanvas.getContext("2d");

            tileCanvas.width = tilesets[ts].width;
            tileCanvas.height = tilesets[ts].height;

            tileContext.drawImage(tilesets[ts], 0, 0);

            var nTilesX = tilesets[ts].width / map.tilewidth;
            var nTilesY = tilesets[ts].height / map.tileheight;

            for (ty = 0; ty < nTilesY; ty++) {
                for (tx = 0; tx < nTilesX; tx++) {
                    var x = tx * map.tilewidth;
                    var y = ty * map.tileheight;

                    var imageData = tileContext.getImageData(x, y, map.tilewidth, map.tileheight);

                    tiles.push(imageData);
                }
            }
        }
        tmxJSON.drawTiles();
    },

    drawTiles: function() {

        for (var l = 0; l < map.layers.length; l++) {
            var x = 0;
            var y = 0;
            if (map.layers[l].type === "tilelayer") {
                for (var d = 0; d < map.layers[l].data.length; d++) {

                    if (d % map.width == 0 && d != 0) {
                        y += map.tileheight;
                        x = 0;
                    }


                    if (map.layers[l].data[d] != 0) {
                        context.putImageData(tiles[map.layers[l].data[d]], x, y);
                    }
                    x += map.tilewidth;
                }

            }
        }

    }
};

}());

EDIT: Alright I've taken the advice of Mokosha and changed the putImageData to drawImage using a separate canvas, but I'm coming up with this error:

GET http://storrdev.com/tmxJSON/[object%20HTMLCanvasElement] 404 (Not Found)

This is something I was running into before when I tried something like this... and the reason I abandoned it. What is happening? I printed all the x and y coordinates for each tile and they all appear to load just fine until the last one... I'm so confused!

Here's the updated code:

(function() {

var tilesets = [];
var tiles = [];
tiles.push(null);

tmxJSON = {

    map: null,

    load: function(jsonFile) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                // Call function for next step in process
                tmxJSON.parse(xhr.responseText);
            }
        }
        xhr.open("GET", jsonFile, true);
        xhr.send();
    },

    parse: function(json) {
        map = eval("(" + json + ")");

        tmxJSON.loadTilesetImages();

    },

    loadTilesetImages: function() {

        var successCount = 0;
        var errorCount = 0;

        for (var ts = 0; ts < map.tilesets.length; ts++) {

            var image = new Image();
            image.addEventListener("load", function() {
                successCount++;
                if (successCount + errorCount == map.tilesets.length) {
                    tmxJSON.separateTiles();
                }
            });
            image.addEventListener("error", function() {
                errorCount++;
                alert("error loading: " + map.tilesets[ts].image);
            });
            image.src = map.tilesets[ts].image;

            tilesets.push(image);

        }


    },

    separateTiles: function() {

        for (var ts = 0; ts < tilesets.length; ts++) {

            var tileCanvas = document.createElement("canvas");
            var tileContext = tileCanvas.getContext("2d");

            tileCanvas.width = map.tilewidth;
            tileCanvas.height = map.tileheight;

            var nTilesX = tilesets[ts].width / map.tilewidth;
            var nTilesY = tilesets[ts].height / map.tileheight;

            for (ty = 0; ty < nTilesY; ty++) {
                for (tx = 0; tx < nTilesX; tx++) {
                    var x = tx * map.tilewidth;
                    var y = ty * map.tileheight;

                    tileContext.drawImage(tilesets[ts], -x, -y);

                    //var imageData = tileContext.getImageData(x, y, map.tilewidth, map.tileheight);\

                    console.log(x + ", " + y);

                    var tile = new Image();
                    tile.src = tileCanvas;

                    tiles.push(tile);
                }
            }
        }
        tmxJSON.drawTiles();
    },

    drawTiles: function() {

        for (var l = 0; l < map.layers.length; l++) {
            var x = 0;
            var y = 0;
            if (map.layers[l].type === "tilelayer") {
                for (var d = 0; d < map.layers[l].data.length; d++) {

                    if (d % map.width == 0 && d != 0) {
                        y += map.tileheight;
                        x = 0;
                    }


                    if (map.layers[l].data[d] != 0) {
                        context.drawImage(tiles[map.layers[l].data[d]], x, y);
                    }
                    x += map.tilewidth;
                }

            }
        }

    }
};

}());
share|improve this question
    
putImageData doesn't "render" in the sense that you're thinking of. It literally copies the pixels into the given rendering context. Depending on what you're trying to do, you probably want to use drawImage to render each tile directly into the context –  Mokosha Oct 2 '13 at 21:36
    
Thanks! Mokosha I've implemented your changes but came up with an error I can't figure out. I've updated my question with the new code and error if you wouldn't mind taking a look. –  saoyr5 Oct 2 '13 at 22:12
add comment

2 Answers

It's not completely clear what you're trying to do, but I assume you have a "sprite sheet" and you're trying to cut out the individual sprites into their own Image objects to later draw on a canvas using a 2d context's drawImage method.

Assuming that's what you want to do, it's not necessary. I've implemented animation using sprite-sheets in my game and I didn't copy any of the images, I just draw them directly from the sheet using drawImage.

share|improve this answer
add comment
up vote 0 down vote accepted

Alright I've found the solution to my problem.

Following Mokosha's advice was the way to go, but in my edit I only went halfway. I found out that when creating an Image() from a separate canvas you need to use the canvas method .toDataURL("image/png");

Here's the website I used to figure that part out: http://davidwalsh.name/convert-canvas-image

You can see my correct code below:

(function() {

var tilesets = [];
var tiles = [];
tiles.push(null);

tmxJSON = {

    map: null,

    load: function(jsonFile) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                // Call function for next step in process
                tmxJSON.parse(xhr.responseText);
            }
        }
        xhr.open("GET", jsonFile, true);
        xhr.send();
    },

    parse: function(json) {
        map = eval("(" + json + ")");

        tmxJSON.loadTilesetImages();

    },

    loadTilesetImages: function() {

        var successCount = 0;
        var errorCount = 0;

        for (var ts = 0; ts < map.tilesets.length; ts++) {

            var image = new Image();
            image.addEventListener("load", function() {
                successCount++;
                if (successCount + errorCount == map.tilesets.length) {
                    tmxJSON.separateTiles();
                }
            });
            image.addEventListener("error", function() {
                errorCount++;
                alert("error loading: " + map.tilesets[ts].image);
            });
            image.src = map.tilesets[ts].image;

            tilesets.push(image);

        }


    },

    separateTiles: function() {

        var successCount = 0;

        for (var ts = 0; ts < tilesets.length; ts++) {

            var nTilesX = tilesets[ts].width / map.tilewidth;
            var nTilesY = tilesets[ts].height / map.tileheight;

            for (ty = 0; ty < nTilesY; ty++) {
                for (tx = 0; tx < nTilesX; tx++) {
                    var tileCanvas = document.createElement("canvas");
                    var tileContext = tileCanvas.getContext("2d");

                    tileCanvas.width = map.tilewidth;
                    tileCanvas.height = map.tileheight;

                    var x = tx * map.tilewidth;
                    var y = ty * map.tileheight;

                    tileContext.drawImage(tilesets[ts], -x, -y);

                    var tile = new Image();
                    tile.src = tileCanvas.toDataURL("image/png");

                    tiles.push(tile);
                }
            }
        }
        tmxJSON.drawTiles();
    },

    drawTiles: function() {

        for (var l = 0; l < map.layers.length; l++) {
            var x = 0;
            var y = 0;
            if (map.layers[l].type === "tilelayer") {
                for (var d = 0; d < map.layers[l].data.length; d++) {

                    if (d % map.width == 0 && d != 0) {
                        y += map.tileheight;
                        x = 0;
                    }


                    if (map.layers[l].data[d] != 0) {
                        context.drawImage(tiles[map.layers[l].data[d]], x, y);
                    }
                    x += map.tilewidth;
                }

            }
        }

    }
};

}());
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.