From df3a1cfa8389449864fd5bfcf8f11767c5fe265e Mon Sep 17 00:00:00 2001 From: Stefan Lau Date: Thu, 4 Jun 2015 22:12:45 +0200 Subject: [PATCH 1/4] Add bin-packing layout algorithm. --- lib/layout/packed.js | 37 +++++++++++++++++++ lib/nsg.js | 1 + package.json | 1 + test/specs/layout/packed.js | 74 +++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 lib/layout/packed.js create mode 100644 test/specs/layout/packed.js diff --git a/lib/layout/packed.js b/lib/layout/packed.js new file mode 100644 index 0000000..aedfde8 --- /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) { + return { + x: image.x, + y: image.y, + width: image.width - options.padding, + height: image.height - options.padding, + path: image.item.path, + data: image.item.data + }; + }); + + 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..1232ce8 --- /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: 0, y: 0 }).extend(images[0]), + _({ x: 30, y: 0 }).extend(images[1]), + _({ x: 0, y: 30 }).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(); + }); + }); + +}); From 05399aa2f1df8619cd01317260899a3ef342b38c Mon Sep 17 00:00:00 2001 From: Stefan Lau Date: Thu, 4 Jun 2015 22:19:45 +0200 Subject: [PATCH 2/4] Update readme for packed layout option. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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` From b652c9f95bcb9aab8f95097f97f64c3bc3bab2d7 Mon Sep 17 00:00:00 2001 From: Stefan Lau Date: Thu, 4 Jun 2015 22:48:08 +0200 Subject: [PATCH 3/4] Remove explicit listing of properties. --- lib/layout/packed.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/layout/packed.js b/lib/layout/packed.js index aedfde8..879c376 100644 --- a/lib/layout/packed.js +++ b/lib/layout/packed.js @@ -19,14 +19,12 @@ module.exports = function generateLayout(images, options, callback) { packed = binPack(images); images = _.map(packed.items, function (image) { - return { + return _.extend({}, image.item, { x: image.x, y: image.y, width: image.width - options.padding, - height: image.height - options.padding, - path: image.item.path, - data: image.item.data - }; + height: image.height - options.padding + }); }); callback(null, { From 6ff85eb863dd6163cc36d25634ba8960fabe1b67 Mon Sep 17 00:00:00 2001 From: Stefan Lau Date: Fri, 5 Jun 2015 07:23:08 +0200 Subject: [PATCH 4/4] Make images centered inside their padded box. --- lib/layout/packed.js | 6 ++++-- test/specs/layout/packed.js | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/layout/packed.js b/lib/layout/packed.js index 879c376..609bd1d 100644 --- a/lib/layout/packed.js +++ b/lib/layout/packed.js @@ -19,9 +19,11 @@ module.exports = function generateLayout(images, options, callback) { packed = binPack(images); images = _.map(packed.items, function (image) { + var paddingOffset = options.padding / 2; + return _.extend({}, image.item, { - x: image.x, - y: image.y, + x: image.x + paddingOffset, + y: image.y + paddingOffset, width: image.width - options.padding, height: image.height - options.padding }); diff --git a/test/specs/layout/packed.js b/test/specs/layout/packed.js index 1232ce8..2a4496b 100644 --- a/test/specs/layout/packed.js +++ b/test/specs/layout/packed.js @@ -41,9 +41,9 @@ describe('Layout/Packed', function () { width: 50, height: 50, images: [ - _({ x: 0, y: 0 }).extend(images[0]), - _({ x: 30, y: 0 }).extend(images[1]), - _({ x: 0, y: 30 }).extend(images[2]) + _({ 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]);