diff --git a/README.md b/README.md index 8d9501e..5e35e82 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ __pixelRatio__ (Type: `Integer` Default: `1`): Specifies the pixelRatio for reti #### options.layout Type: `String|Function` Default value: `'vertical'` -The layout that is used to generate the sprite. The built-in layouts are `'vertical'`, `'horizontal'` and `'diagonal'`. You can also specify a function that generates a custom layout (see more at [extending node-sprite-generator](https://github.com/selaux/node-sprite-generator#extending-node-sprite-generator)). +The layout that is used to generate the sprite. The built-in layouts are `'packed'` (for bin-packing), `'vertical'`, `'horizontal'` and `'diagonal'`. You can also specify a function that generates a custom layout (see more at [extending node-sprite-generator](https://github.com/selaux/node-sprite-generator#extending-node-sprite-generator)). #### options.layoutOptions Type: `Object` diff --git a/lib/layout/packed.js b/lib/layout/packed.js new file mode 100644 index 0000000..609bd1d --- /dev/null +++ b/lib/layout/packed.js @@ -0,0 +1,37 @@ +'use strict'; + +var _ = require('underscore'), + binPack = require('bin-pack'), + defaultOptions = require('./utils/defaultOptions'), + scaleImages = require('./utils/scaleImages'); + +module.exports = function generateLayout(images, options, callback) { + var packed; + + options = _.extend({}, defaultOptions, options); + + images = scaleImages(images, options); + images = _.map(images, function (image) { + image.width += options.padding; + image.height += options.padding; + return image; + }); + + packed = binPack(images); + images = _.map(packed.items, function (image) { + var paddingOffset = options.padding / 2; + + return _.extend({}, image.item, { + x: image.x + paddingOffset, + y: image.y + paddingOffset, + width: image.width - options.padding, + height: image.height - options.padding + }); + }); + + callback(null, { + width: packed.width, + height: packed.height, + images: images + }); +}; diff --git a/lib/nsg.js b/lib/nsg.js index f53f09e..1ca00b8 100644 --- a/lib/nsg.js +++ b/lib/nsg.js @@ -7,6 +7,7 @@ var async = require('async'), providedLayouts = { 'diagonal': require('./layout/diagonal'), 'horizontal': require('./layout/horizontal'), + 'packed': require('./layout/packed'), 'vertical': require('./layout/vertical') }, providedCompositors = {}, diff --git a/package.json b/package.json index 177ccf2..51f916b 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "lib/nsg.js", "dependencies": { "async": "1.2.0", + "bin-pack": "1.0.1", "glob": "5.0.10", "underscore": "1.8.2" }, diff --git a/test/specs/layout/packed.js b/test/specs/layout/packed.js new file mode 100644 index 0000000..2a4496b --- /dev/null +++ b/test/specs/layout/packed.js @@ -0,0 +1,74 @@ +'use strict'; + +var expect = require('chai').expect, + _ = require('underscore'), + packed = require('../../../lib/layout/packed.js'); + +describe('Layout/Packed', function () { + var images = [ + { path: 'foo', width: 20, height: 20, data: 'image1' }, + { path: 'bar', width: 10, height: 10, data: 'image2' }, + { path: 'bla', width: 10, height: 10, data: 'image3' } + ]; + + it('should generate the correct layout without any options', function (done) { + var options = {}; + + packed(images, options, function (err, layout) { + expect(err).not.to.be.ok; + expect(options).to.deep.equal({}); + expect(layout).to.deep.equal({ + width: 30, + height: 20, + images: [ + _({ x: 0, y: 0 }).extend(images[0]), + _({ x: 20, y: 0 }).extend(images[1]), + _({ x: 20, y: 10 }).extend(images[2]) + ] + }); + expect(layout.images[0]).not.to.equal(images[0]); + done(); + }); + }); + + it('should generate the correct layout when a padding is specified', function (done) { + var options = { padding: 10 }; + + packed(images, options, function (err, layout) { + expect(err).not.to.be.ok; + expect(options).to.deep.equal({ padding: 10 }); + expect(layout).to.deep.equal({ + width: 50, + height: 50, + images: [ + _({ x: 5, y: 5 }).extend(images[0]), + _({ x: 35, y: 5 }).extend(images[1]), + _({ x: 5, y: 35 }).extend(images[2]) + ] + }); + expect(layout.images[0]).not.to.equal(images[0]); + done(); + }); + }); + + it('should generate the correct layout when a scaling is specified', function (done) { + var options = { scaling: 0.5 }; + + packed(images, options, function (err, layout) { + expect(err).not.to.be.ok; + expect(options).to.deep.equal({ scaling: 0.5 }); + expect(layout).to.deep.equal({ + width: 15, + height: 10, + images: [ + _.extend({}, images[0], { x: 0, y: 0, width: 10, height: 10 }), + _.extend({}, images[1], { x: 10, y: 0, width: 5, height: 5 }), + _.extend({}, images[2], { x: 10, y: 5, width: 5, height: 5 }) + ] + }); + expect(layout.images[0]).not.to.equal(images[0]); + done(); + }); + }); + +});