diff --git a/README.md b/README.md index 7d88ba79..34e0a84e 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ This is a branch based off of [@kant2002/lwip](https://www.npmjs.com/package/@ka 0. [GIF](#gif) 0. [Write to file](#write-to-file) 0. [Get metadata](#get-metadata) + 0. [Get dominant color](#get-dominant-color) 0. [Batch operations](#batch-operations) 0. [Copyrights](#copyrights) @@ -739,6 +740,14 @@ tEXt chunks in PNG images, and will get the first tEXt chunk found with the key `image.getMetadata()` +### Get dominant color + +Get the pixel color that occurs most frequently in a picture. + +`image.dominantColor(pixels_to_skip)` + +0. `pixels_to_skip {Int}`: the number of pixels to skip while iterating through the image. Ex. supplying 1 will skip every other pixel, 0 will skip none. The greater the number the less accuracy the result will have. + ### Batch operations Each of the [image operations](#image-operations) above can be done as part of diff --git a/lib/Image.js b/lib/Image.js index 48c5a2b1..e07624af 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -34,7 +34,8 @@ const judges = { toBuffer: decree(defs.args.toBuffer), writeFile: decree(defs.args.writeFile), setPixel: decree(defs.args.setPixel), - getPixel: decree(defs.args.getPixel) + getPixel: decree(defs.args.getPixel), + dominantColor: decree(defs.args.dominantColor) }; module.exports = class Image { @@ -82,6 +83,47 @@ module.exports = class Image { }; } + dominantColor () { + let dominantColor; + judges.dominantColor( + arguments, + skips => { + if (typeof skips !== 'number' || skips < 0 || parseInt(skips,10) !== skips) { + throw Error('Pass a positive integer argument for number of pixels to skip over each iteration'); + } + + const colorCounter = {}; + for (let i = 0; i < this.width(); i++) { + for (let j = 0; j < this.height(); j+= skips+1) { + const pixel = this.__lwip.getPixel(i,j), + pixelColor = pixel[0].toString()+','+pixel[1].toString()+','+pixel[2].toString()+','+pixel[3].toString(), + occurence = colorCounter[pixelColor]; + if (occurence === undefined) colorCounter[pixelColor] = 1; + else colorCounter[pixelColor]++; + } + } + + let the_key = '0,0,0,0', + count = 0; + for (let key in colorCounter) { + if (colorCounter[key] > count) { + the_key = key; + count = colorCounter[key]; + } + } + the_key = the_key.split(','); + + dominantColor = { + r: parseInt(the_key[0],10), + g: parseInt(the_key[1],10), + b: parseInt(the_key[2],10), + a: parseInt(the_key[3],10) + }; + } + ); + return dominantColor; + } + getPixel () { const args = judges.getPixel(arguments), left = args[0], diff --git a/lib/defs.js b/lib/defs.js index df9feec3..4523fc24 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -192,214 +192,218 @@ exports.args = { name: 'callback', type: 'function' }], - darken: [{ - name: 'delta', - type: 'number' - }, { - name: 'callback', - type: 'function' - }], - fade: [{ - name: 'delta', - type: 'number' - }, { - name: 'callback', - type: 'function' - }], - opacify: [{ - name: 'callback', - type: 'function' - }], - hue: [{ - name: 'shift', - type: 'number' - }, { - name: 'callback', - type: 'function' - }], - crop: [{ - name: 'left', - type: 'nn-number' - }, { - name: 'top', - type: 'nn-number' - }, { - name: 'right', - type: 'nn-number', - optional: true - }, { - name: 'bottom', - type: 'nn-number', - optional: true - }, { - name: 'callback', - type: 'function' - }], - mirror: [{ - name: 'axes', - type: 'axes' - }, { - name: 'callback', - type: 'function' - }], - exec: [{ - name: 'callback', - type: 'function' - }], - toBuffer: [{ - name: 'type', - type: 'string' - }, { - name: 'params', - type: 'hash', - optional: true, - default: {} - }, { - name: 'callback', - type: 'function' - }], - getPixel: [{ - name: 'left', - type: 'nn-int' - }, { - name: 'top', - type: 'nn-int' - }], - border: [{ - name: 'width', - type: 'nn-number' - }, { - name: 'color', - type: 'color', - optional: true, - default: defaults.DEF_BORDER_COLOR - }, { - name: 'callback', - type: 'function' - }], - pad: [{ - name: 'left', - type: 'nn-number' - }, { - name: 'top', - type: 'nn-number' - }, { - name: 'right', - type: 'nn-number' - }, { - name: 'bottom', - type: 'nn-number' - }, { - name: 'color', - type: 'color', - optional: true, - default: defaults.DEF_PAD_COLOR - }, { - name: 'callback', - type: 'function' - }], - sharpen: [{ - name: 'amplitude', - type: 'number' - }, { - name: 'callback', - type: 'function' - }], - clone: [{ - name: 'callback', - type: 'function' - }], - paste: [{ - name: 'left', - type: 'nn-number' - }, { - name: 'top', - type: 'nn-number' - }, { - name: 'image', - type: 'image' - }, { - name: 'callback', - type: 'function' - }], - extract: [{ - name: 'left', - type: 'nn-number' - }, { - name: 'top', - type: 'nn-number' - }, { - name: 'right', - type: 'nn-number' - }, { - name: 'bottom', - type: 'nn-number' - }, { - name: 'callback', - type: 'function' - }], - writeFile: [{ - name: 'path', - type: 'string' - }, { - name: 'type', - type: 'string', - optional: true - }, { - name: 'params', - type: 'hash', - optional: true, - default: {} - }, { - name: 'callback', - type: 'function' - }], - setPixel: [{ - name: 'left', - type: 'nn-int' - }, { - name: 'top', - type: 'nn-int' - }, { - name: 'color', - type: 'color' - }, { - name: 'callback', - type: 'function' - }], - contain: [{ - name: 'width', - type: 'p-number' - }, { - name: 'height', - type: 'p-number' - }, { - name: 'color', - type: 'color', - optional: true, - default: defaults.DEF_CONTAIN_COLOR - }, { - name: 'interpolation', - type: 'interpolation', - optional: true, - default: defaults.DEF_INTERPOLATION - }, { - name: 'callback', - type: 'function' - }], - cover: [{ - name: 'width', - type: 'p-number' - }, { - name: 'height', - type: 'p-number' - }, { - name: 'interpolation', - type: 'interpolation', - optional: true, - default: defaults.DEF_INTERPOLATION - }, { - name: 'callback', - type: 'function' - }], -}; + dominantColor: [{ + name: 'type', + type: 'number' + }], + darken: [{ + name: 'delta', + type: 'number' + }, { + name: 'callback', + type: 'function' + }], + fade: [{ + name: 'delta', + type: 'number' + }, { + name: 'callback', + type: 'function' + }], + opacify: [{ + name: 'callback', + type: 'function' + }], + hue: [{ + name: 'shift', + type: 'number' + }, { + name: 'callback', + type: 'function' + }], + crop: [{ + name: 'left', + type: 'nn-number' + }, { + name: 'top', + type: 'nn-number' + }, { + name: 'right', + type: 'nn-number', + optional: true + }, { + name: 'bottom', + type: 'nn-number', + optional: true + }, { + name: 'callback', + type: 'function' + }], + mirror: [{ + name: 'axes', + type: 'axes' + }, { + name: 'callback', + type: 'function' + }], + exec: [{ + name: 'callback', + type: 'function' + }], + toBuffer: [{ + name: 'type', + type: 'string' + }, { + name: 'params', + type: 'hash', + optional: true, + default: {} + }, { + name: 'callback', + type: 'function' + }], + getPixel: [{ + name: 'left', + type: 'nn-int' + }, { + name: 'top', + type: 'nn-int' + }], + border: [{ + name: 'width', + type: 'nn-number' + }, { + name: 'color', + type: 'color', + optional: true, + default: defaults.DEF_BORDER_COLOR + }, { + name: 'callback', + type: 'function' + }], + pad: [{ + name: 'left', + type: 'nn-number' + }, { + name: 'top', + type: 'nn-number' + }, { + name: 'right', + type: 'nn-number' + }, { + name: 'bottom', + type: 'nn-number' + }, { + name: 'color', + type: 'color', + optional: true, + default: defaults.DEF_PAD_COLOR + }, { + name: 'callback', + type: 'function' + }], + sharpen: [{ + name: 'amplitude', + type: 'number' + }, { + name: 'callback', + type: 'function' + }], + clone: [{ + name: 'callback', + type: 'function' + }], + paste: [{ + name: 'left', + type: 'nn-number' + }, { + name: 'top', + type: 'nn-number' + }, { + name: 'image', + type: 'image' + }, { + name: 'callback', + type: 'function' + }], + extract: [{ + name: 'left', + type: 'nn-number' + }, { + name: 'top', + type: 'nn-number' + }, { + name: 'right', + type: 'nn-number' + }, { + name: 'bottom', + type: 'nn-number' + }, { + name: 'callback', + type: 'function' + }], + writeFile: [{ + name: 'path', + type: 'string' + }, { + name: 'type', + type: 'string', + optional: true + }, { + name: 'params', + type: 'hash', + optional: true, + default: {} + }, { + name: 'callback', + type: 'function' + }], + setPixel: [{ + name: 'left', + type: 'nn-int' + }, { + name: 'top', + type: 'nn-int' + }, { + name: 'color', + type: 'color' + }, { + name: 'callback', + type: 'function' + }], + contain: [{ + name: 'width', + type: 'p-number' + }, { + name: 'height', + type: 'p-number' + }, { + name: 'color', + type: 'color', + optional: true, + default: defaults.DEF_CONTAIN_COLOR + }, { + name: 'interpolation', + type: 'interpolation', + optional: true, + default: defaults.DEF_INTERPOLATION + }, { + name: 'callback', + type: 'function' + }], + cover: [{ + name: 'width', + type: 'p-number' + }, { + name: 'height', + type: 'p-number' + }, { + name: 'interpolation', + type: 'interpolation', + optional: true, + default: defaults.DEF_INTERPOLATION + }, { + name: 'callback', + type: 'function' + }], + }; diff --git a/tests/01.getters/index.js b/tests/01.getters/index.js index 9d8be348..7c95fe75 100644 --- a/tests/01.getters/index.js +++ b/tests/01.getters/index.js @@ -103,3 +103,44 @@ describe('lwip.getMetadata', () => { }); }); }); + +describe('lwip.dominantColor',function(){ + var t_image; + before(function(done){ + lwip.open(imgs.jpg.colors, function(err, img) { + if (err) return done(err); + t_image = img; + done(); + }); + }); + + it('should return the color that occurs the most frequently',function(done){ + var color = t_image.dominantColor(10); + assert( color.r === 255); + assert( color.g === 252); + assert( color.b === 0); + assert( color.a === 100); + done(); + }); + + function shouldFail(fail){ + it('should fail when: '+fail+' is passed as a parameter',function(done){ + var err_count = 0; + try{ + t_image.dominantColor(fail); + } + catch(error){ + err_count++; + } + finally{ + assert( err_count === 1); + } + done(); + }); + } + + var fails = [ -1, 3.3, '\'tree\'']; + for(var i = 0; i < fails.length; i++) + shouldFail(fails[i]); + +}); diff --git a/tests/images/colors.jpg b/tests/images/colors.jpg new file mode 100644 index 00000000..772a62ff Binary files /dev/null and b/tests/images/colors.jpg differ diff --git a/tests/imgs.js b/tests/imgs.js index 0c1d0f39..3e6e0efd 100644 --- a/tests/imgs.js +++ b/tests/imgs.js @@ -7,7 +7,9 @@ module.exports = { gs: join(__dirname, imbase, 'gs.jpg'), rgb: join(__dirname, imbase, 'rgb.jpg'), noex: join(__dirname, imbase, 'rgbjpg'), - inv: join(__dirname, imbase, 'invalid.jpg') + inv: join(__dirname, imbase, 'invalid.jpg'), + colors: join(__dirname, imbase, 'colors.jpg') + }, png: { gs: join(__dirname, imbase, 'gs.png'),