Skip to content

Instantly share code, notes, and snippets.

@jonbro
Created September 2, 2012 16:43
Show Gist options
  • Select an option

  • Save jonbro/3601339 to your computer and use it in GitHub Desktop.

Select an option

Save jonbro/3601339 to your computer and use it in GitHub Desktop.
html sprite packer
<!DOCTYPE html>
<!--
instructions:
drag a set of images onto the canvas.
it will create a spritesheet from these images.
credits:
http://robertnyman.com/2011/03/10/using-html5-canvas-drag-and-drop-and-file-api-to-offer-the-cure/
http://www.blackpawn.com/texts/lightmaps/
http://www.html5rocks.com/en/tutorials/file/dndfiles/
-->
<html>
<head>
<meta charset="utf-8">
<title>Canvas Sprite Packing</title>
<style>
canvas {
position: relative;
border: 1px solid #000;
}
</style>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
</head>
<body>
<div id="container">
<header role="banner">
<h1>Canvas Sprite Packing</h1>
</header>
<div role="main">
<section id="main-content">
<canvas id="my-canvas" width="1024" height="1024">I am canvas</canvas>
<br/>
<textarea id="datafile"></textarea>
<script>
(function () {
var canvas = document.getElementById("my-canvas"),
context = canvas.getContext("2d"),
img = document.createElement("img"),
mouseDown = false,
brushColor = "rgb(0, 0, 0)",
hasText = true,
clearCanvas = function () {
if (hasText) {
context.clearRect(0, 0, canvas.width, canvas.height);
hasText = false;
}
};
// Adding instructions
context.fillText("Drop an image onto the canvas", 240, 200);
context.fillText("Click a spot to set as brush color", 240, 220);
// Image for loading
img.addEventListener("load", function () {
//clearCanvas();
context.drawImage(img, 0, 0);
}, false);
// To enable drag and drop
canvas.addEventListener("dragover", function (evt) {
evt.preventDefault();
}, false);
// Handle dropped image file - only Firefox and Google Chrome
canvas.addEventListener("drop", function (evt) {
clearCanvas();
var files = evt.dataTransfer.files;
if (files.length > 0) {
var loadCount = 0;
var images = new Array();
for(var i=0;i<files.length;i++){
console.log('adding file: '+i);
var file = files[i];
if (typeof FileReader !== "undefined" && file.type.indexOf("image") != -1) {
loadCount++;
var getImgLoader = function(){
var img = document.createElement("img");
img.addEventListener("load", function () {
loadCount--;
if(loadCount==0){
console.log('building atlas');
buildAtlas(images);
}
}, false);
img.readerOnLoad = function (evt) {
img.src = evt.target.result;
};
return img;
}
var reader = new FileReader();
// Note: addEventListener doesn't work in Google Chrome for this event
var img = getImgLoader();
img.filename = file.name;
images.push(img);
reader.onload = img.readerOnLoad;
reader.readAsDataURL(file);
}
}
}
evt.preventDefault();
}, false);
var Rect = function(x, y, w, h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
};
var ImgNode = function(rect){
this.rect = rect;
this.child = new Array();
this.isLeaf = function(){
return !this.child[0] && !this.child[1];
}
this.insertImg = function(img){
if(!this.isLeaf()){
var newNode = this.child[0].insertImg(img);
if(newNode){
return newNode;
}else{
return this.child[1].insertImg(img);
}
}else{
console.log('no longer a leaf');
if(this.img){
return false;
}
if(img.width > this.rect.w || img.height > this.rect.h){
return false;
}
if(img.width == this.rect.w && img.height == this.rect.h){
return this;
}
// otherwise we need to split the node
var dw = this.rect.w - img.width;
var dh = this.rect.h - img.height;
if(dw>dh){
this.child[0] = new ImgNode(new Rect(this.rect.x, this.rect.y, img.width, this.rect.h));
this.child[1] = new ImgNode(new Rect(this.rect.x+img.width, this.rect.y, this.rect.w-img.width, this.rect.h));
}else{
this.child[0] = new ImgNode(new Rect(this.rect.x, this.rect.y, this.rect.w, img.height));
this.child[1] = new ImgNode(new Rect(this.rect.x, this.rect.y+img.height, this.rect.w, this.rect.h - img.height));
}
return this.child[0].insertImg(img);
}
}
};
var removePadding = function(img, completeCallback){
// just a hack to make this syncronous
var complete = false;
var newCanvas = document.createElement('canvas');
newCanvas.width=img.width;
newCanvas.height=img.height;
document.body.appendChild(newCanvas);
newCanvas.getContext('2d').drawImage(img, 0, 0);
// determine the width and height of the image
var minX = img.width;
var minY = img.height;
var maxX = 1;
var maxY = 1;
var imageData = newCanvas.getContext('2d').getImageData(0,0,img.width, img.height);
console.log('canvas size', imageData.width, imageData.height);
for (x = 0; x < imageData.width; x++) {
for (y = 0; y < imageData.height; y++) {
var index = (x + y * imageData.width) * 4;
if(imageData.data[index+3]>0){
maxX = Math.max(x, maxX);
maxY = Math.max(y, maxY);
minX = Math.min(x, minX);
minY = Math.min(y, minY);
}
}
}
console.log('minimums', minX, minY);
newCanvas.width = maxX+2-minX;
newCanvas.height = maxY+2-minY;
newCanvas.getContext('2d').drawImage(img, -minX, -minY);
img.onload = function(){
img.offX = minX;
img.offY = minY;
completeCallback();
};
img.src = newCanvas.toDataURL("image/png");
document.body.removeChild(newCanvas);
return img;
};
var buildAtlas = function(images){
var output = new Array();
var RootNode = new ImgNode(new Rect(0,0,1024,1024));
var removePaddingCount = images.length;
for (var i = images.length - 1; i >= 0; i--) {
output[i] = {
filename: images[i].filename,
sourceSize: {
w:images[i].width,
h:images[i].height
}
};
images[i] = removePadding(images[i], function(){
removePaddingCount--;
if(removePaddingCount==0){
for (var i = images.length - 1; i >= 0; i--) {
var node = RootNode.insertImg(images[i]);
if(node !== false){
node.img = images[i];
output[i].frame = {
x: node.rect.x,
y: node.rect.y,
w: node.rect.w,
h: node.rect.h
};
output[i].sourceSize.x = images[i].offX;
output[i].sourceSize.y = images[i].offY;
context.drawImage(images[i], node.rect.x, node.rect.y);
}
};
// output to the datafile
document.getElementById("datafile").value = JSON.stringify(output);
}
});
}
};
// Save image
var saveImage = document.createElement("button");
saveImage.innerHTML = "Save canvas";
saveImage.addEventListener("click", function (evt) {
window.open(canvas.toDataURL("image/png"));
evt.preventDefault();
}, false);
document.getElementById("main-content").appendChild(saveImage);
})();
</script>
</section>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment