From c3854e3a5adbdd422cc7bb3a94e9b11e28596019 Mon Sep 17 00:00:00 2001 From: Ramin Mousavi Date: Sat, 9 Dec 2017 18:23:10 +0330 Subject: [PATCH 1/3] read image from remote url --- .gitignore | 2 ++ nude.js | 7 +++++-- package.json | 9 +++++++-- readme.md | 7 +++++-- tests/test.js | 30 +++++++++++++++++------------- 5 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f19d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/nude.js b/nude.js index a9b040b..7a464f0 100644 --- a/nude.js +++ b/nude.js @@ -1,3 +1,4 @@ +var request = require('request').defaults({ encoding: null }) var Canvas = require('canvas'), fs = require('fs'), Image = Canvas.Image; @@ -37,10 +38,12 @@ var classifySkin = function(r, g, b) { }; module.exports = { - scan: function(src, callback) { - fs.readFile(src, function(err, data) { + scan: function(url, callback) { + request.get(url, function (err, res, data) { + if(err) throw err; + var canvas = new Canvas(1, 1), ctx = canvas.getContext('2d'), img = new Image, diff --git a/package.json b/package.json index 7d29f5e..cddcf13 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,11 @@ "author": "Chad Smith (http://twitter.com/chadsmith)", "name": "nude", "description": "Nudity detection for Node.js", - "keywords": ["images", "filter", "detect"], + "keywords": [ + "images", + "filter", + "detect" + ], "version": "0.1", "homepage": "https://github.com/chadsmith/node-nude", "repository": { @@ -14,6 +18,7 @@ }, "main": "./nude.js", "dependencies": { - "canvas": "*" + "canvas": "^1.6.7", + "request": "^2.83.0" } } diff --git a/readme.md b/readme.md index ec448b9..70e6c83 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,9 @@ Node.js port of [nude.js](https://github.com/pa7/nude.js) by [Patrick Wied](https://github.com/pa7). +## Why I forked this package +My fork read images from a remote url instead of local disk + ## Installation `npm install nude` @@ -9,7 +12,7 @@ Node.js port of [nude.js](https://github.com/pa7/nude.js) by [Patrick Wied](http ## Usage overview var nude = require('nude'); - - nude.scan(__dirname + '/images/photo.jpg', function(res) { + + nude.scan('', function(res) { console.log('Contains nudity: ' + res); }); diff --git a/tests/test.js b/tests/test.js index b3576e4..464848b 100644 --- a/tests/test.js +++ b/tests/test.js @@ -1,14 +1,18 @@ -var nude = require('../nude'), - images = [ - { id: 1, expected: false }, - { id: 2, expected: false }, - { id: 3, expected: false }, - { id: 4, expected: true } - ]; +// var nude = require('../nude'), +// images = [ +// { id: 1, expected: false }, +// { id: 2, expected: false }, +// { id: 3, expected: false }, +// { id: 4, expected: true } +// ]; -images.forEach(function(image) { - nude.scan(__dirname + '/images/' + image.id + '.jpg', function(res) { - console.log('Image ' + image.id + ': ' + res); - console.log('Expected: ' + image.expected); - }); -}); +// images.forEach(function(image) { +// nude.scan(__dirname + '/images/' + image.id + '.jpg', function(res) { +// console.log('Image ' + image.id + ': ' + res); +// console.log('Expected: ' + image.expected); +// }); +// }); +var nude = require('../nude') +nude.scan('https://i.ytimg.com/vi/FLdK7Hatm0w/hqdefault.jpg', function(res) { + console.log(res) +}) From 5a60c4714fb912e92de65f5935b91efde9c31011 Mon Sep 17 00:00:00 2001 From: Ramin Mousavi Date: Mon, 11 Dec 2017 01:38:45 +0330 Subject: [PATCH 2/3] read image from url, add async functions --- nude.js | 324 ++++++++++++++++++++++++++++---------------------- readme.md | 20 ++-- tests/test.js | 45 ++++--- 3 files changed, 227 insertions(+), 162 deletions(-) diff --git a/nude.js b/nude.js index 7a464f0..3cce0d3 100644 --- a/nude.js +++ b/nude.js @@ -37,148 +37,194 @@ var classifySkin = function(r, g, b) { return [r / sum, g / sum, b / sum]; }; -module.exports = { - scan: function(url, callback) { - request.get(url, function (err, res, data) { - if(err) - throw err; +function scanUrlAsync(url) { + return new Promise((resolve, reject) => { + scanUrl(url, function(result, err) { + if (err) + return reject(err) + + return resolve(result) + }) + }) +} + +function scanAsync(src) { + return new Promise((resolve, reject) => { + scan(src, function(result, err) { + if (err) + return reject(err) + + return resolve(result) + }) + }) +} + +function scanUrl(url, callback) { + request({ + method: 'GET', + uri: url, + timeout: 6 * 1000, + gzip: true + }, function (err, res, data) { + if(err) { + return callback && callback(null, err) + } + + process(data, callback) + }) +} - var canvas = new Canvas(1, 1), - ctx = canvas.getContext('2d'), - img = new Image, - skinRegions = [], - skinMap = [], - detectedRegions = [], - mergeRegions = [], - detRegions = [], - lastFrom = -1, - lastTo = -1, - totalSkin = 0, - addMerge = function(from, to) { - lastFrom = from; - lastTo = to; - var len = mergeRegions.length, - fromIndex = -1, - toIndex = -1, - region, - rlen; - while(len--) { - region = mergeRegions[len]; - rlen = region.length; - while(rlen--) { - if(region[rlen] == from) - fromIndex = len; - if(region[rlen] == to) - toIndex = len; - } - } - if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex) - return; - if(fromIndex == -1 && toIndex == -1) - return mergeRegions.push([from, to]); - if(fromIndex != -1 && toIndex == -1) - return mergeRegions[fromIndex].push(to); - if(fromIndex == -1 && toIndex != -1) - return mergeRegions[toIndex].push(from); - if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex) { - mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]); - mergeRegions = [].concat(mergeRegions.slice(0, toIndex), mergeRegions.slice(toIndex + 1)); - } - }, - totalPixels, - imageData, - length; - img.src = data; - canvas.width = img.width; - canvas.height = img.height; - totalPixels = canvas.width * canvas.height; - ctx.drawImage(img, 0, 0); - imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; - length = imageData.length; - for(var i = 0, u = 1; i < length; i += 4, u++) { - var r = imageData[i], - g = imageData[i + 1], - b = imageData[i + 2], - x = u > canvas.width ? u % canvas.width - 1 : u, - y = u > canvas.width ? Math.ceil(u / canvas.width) - 1 : 1; - if(classifySkin(r, g, b)) { - skinMap.push({id: u, skin: true, region: 0, x: x, y: y, checked: false}); - var region = -1, - checkIndexes = [u - 2, u - canvas.width - 2, u - canvas.width - 1, u - canvas.width], - checker = false; - for(var o = 0, index; o < 4; o++) { - index = checkIndexes[o]; - if(skinMap[index] && skinMap[index].skin) { - if(skinMap[index].region != region && region != -1 && lastFrom != region && lastTo != skinMap[index].region) - addMerge(region, skinMap[index].region); - region = skinMap[index].region; - checker = true; - } - } - if(!checker) { - skinMap[u - 1].region = detectedRegions.length; - detectedRegions.push([skinMap[u - 1]]); - continue; - } - else - if(region > -1) { - if(!detectedRegions[region]) - detectedRegions[region] = []; - skinMap[u - 1].region = region; - detectedRegions[region].push(skinMap[u - 1]); - } +function scan(src, callback) { + fs.readFile(src, function(err, data) { + if(err) + return callback && callback(null, err) + + process(data, callback) + }) +} + +function process(data, callback) { + var canvas = new Canvas(1, 1), + ctx = canvas.getContext('2d'), + img = new Image, + skinRegions = [], + skinMap = [], + detectedRegions = [], + mergeRegions = [], + detRegions = [], + lastFrom = -1, + lastTo = -1, + totalSkin = 0, + addMerge = function(from, to) { + lastFrom = from; + lastTo = to; + var len = mergeRegions.length, + fromIndex = -1, + toIndex = -1, + region, + rlen; + while(len--) { + region = mergeRegions[len]; + rlen = region.length; + while(rlen--) { + if(region[rlen] == from) + fromIndex = len; + if(region[rlen] == to) + toIndex = len; + } + } + if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex) + return; + if(fromIndex == -1 && toIndex == -1) + return mergeRegions.push([from, to]); + if(fromIndex != -1 && toIndex == -1) + return mergeRegions[fromIndex].push(to); + if(fromIndex == -1 && toIndex != -1) + return mergeRegions[toIndex].push(from); + if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex) { + mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]); + mergeRegions = [].concat(mergeRegions.slice(0, toIndex), mergeRegions.slice(toIndex + 1)); + } + }, + totalPixels, + imageData, + length; + img.src = data; + canvas.width = img.width; + canvas.height = img.height; + totalPixels = canvas.width * canvas.height; + ctx.drawImage(img, 0, 0); + imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + length = imageData.length; + for(var i = 0, u = 1; i < length; i += 4, u++) { + var r = imageData[i], + g = imageData[i + 1], + b = imageData[i + 2], + x = u > canvas.width ? u % canvas.width - 1 : u, + y = u > canvas.width ? Math.ceil(u / canvas.width) - 1 : 1; + if(classifySkin(r, g, b)) { + skinMap.push({id: u, skin: true, region: 0, x: x, y: y, checked: false}); + var region = -1, + checkIndexes = [u - 2, u - canvas.width - 2, u - canvas.width - 1, u - canvas.width], + checker = false; + for(var o = 0, index; o < 4; o++) { + index = checkIndexes[o]; + if(skinMap[index] && skinMap[index].skin) { + if(skinMap[index].region != region && region != -1 && lastFrom != region && lastTo != skinMap[index].region) + addMerge(region, skinMap[index].region); + region = skinMap[index].region; + checker = true; } - else - skinMap.push({ id: u, skin: false, region: 0, x: x, y: y, checked: false }); } - length = mergeRegions.length; - while(length--) { - region = mergeRegions[length]; - var rlen = region.length; - if(!detRegions[length]) - detRegions[length] = []; - while(rlen--) { - index = region[rlen]; - detRegions[length] = detRegions[length].concat(detectedRegions[index]); - detectedRegions[index] = []; - } + if(!checker) { + skinMap[u - 1].region = detectedRegions.length; + detectedRegions.push([skinMap[u - 1]]); + continue; } - length = detectedRegions.length; - while(length--) - if(detectedRegions[length].length > 0) - detRegions.push(detectedRegions[length]); - length = detRegions.length; - for(var i = 0; i < length; i++) - if(detRegions[i].length > 30) - skinRegions.push(detRegions[i]); - length = skinRegions.length; - if(length < 3) - return callback && callback(false); - (function() { - var sorted = false, temp; - while(!sorted) { - sorted = true; - for(var i = 0; i < length-1; i++) - if(skinRegions[i].length < skinRegions[i + 1].length) { - sorted = false; - temp = skinRegions[i]; - skinRegions[i] = skinRegions[i + 1]; - skinRegions[i + 1] = temp; - } + else + if(region > -1) { + if(!detectedRegions[region]) + detectedRegions[region] = []; + skinMap[u - 1].region = region; + detectedRegions[region].push(skinMap[u - 1]); } - })(); - while(length--) - totalSkin += skinRegions[length].length; - if((totalSkin / totalPixels) * 100 < 15) - return callback && callback(false); - if((skinRegions[0].length / totalSkin) * 100 < 35 && (skinRegions[1].length / totalSkin) * 100 < 30 && (skinRegions[2].length / totalSkin) * 100 < 30) - return callback && callback(false); - if((skinRegions[0].length / totalSkin) * 100 < 45) - return callback && callback(false); - if(skinRegions.length > 60) - return callback && callback(false); - return callback && callback(true); - }); - } -}; + } + else + skinMap.push({ id: u, skin: false, region: 0, x: x, y: y, checked: false }); + } + length = mergeRegions.length; + while(length--) { + region = mergeRegions[length]; + var rlen = region.length; + if(!detRegions[length]) + detRegions[length] = []; + while(rlen--) { + index = region[rlen]; + detRegions[length] = detRegions[length].concat(detectedRegions[index]); + detectedRegions[index] = []; + } + } + length = detectedRegions.length; + while(length--) + if(detectedRegions[length].length > 0) + detRegions.push(detectedRegions[length]); + length = detRegions.length; + for(var i = 0; i < length; i++) + if(detRegions[i].length > 30) + skinRegions.push(detRegions[i]); + length = skinRegions.length; + if(length < 3) + return callback && callback(false); + (function() { + var sorted = false, temp; + while(!sorted) { + sorted = true; + for(var i = 0; i < length-1; i++) + if(skinRegions[i].length < skinRegions[i + 1].length) { + sorted = false; + temp = skinRegions[i]; + skinRegions[i] = skinRegions[i + 1]; + skinRegions[i + 1] = temp; + } + } + })(); + while(length--) + totalSkin += skinRegions[length].length; + if((totalSkin / totalPixels) * 100 < 15) + return callback && callback(false); + if((skinRegions[0].length / totalSkin) * 100 < 35 && (skinRegions[1].length / totalSkin) * 100 < 30 && (skinRegions[2].length / totalSkin) * 100 < 30) + return callback && callback(false); + if((skinRegions[0].length / totalSkin) * 100 < 45) + return callback && callback(false); + if(skinRegions.length > 60) + return callback && callback(false); + return callback && callback(true); +} + +module.exports = { + scan: scan, + scanAsync: scanAsync, + scanUrl: scanUrl, + scanUrlAsync: scanUrlAsync +} diff --git a/readme.md b/readme.md index 70e6c83..83a9108 100644 --- a/readme.md +++ b/readme.md @@ -2,17 +2,23 @@ Node.js port of [nude.js](https://github.com/pa7/nude.js) by [Patrick Wied](https://github.com/pa7). -## Why I forked this package -My fork read images from a remote url instead of local disk - ## Installation `npm install nude` ## Usage overview +``` + var nude = require('nude'); + + nude.scan('', function(res) { + console.log('Contains nudity: ' + res); + }); + + var isNude = await nude.scanAsync(''); - var nude = require('nude'); + nude.scanUrl('', function(res) { + console.log('Contains nudity: ' + res); + }); - nude.scan('', function(res) { - console.log('Contains nudity: ' + res); - }); + var isNude = await nude.scanUrlAsync(''); +``` diff --git a/tests/test.js b/tests/test.js index 464848b..16c883b 100644 --- a/tests/test.js +++ b/tests/test.js @@ -1,18 +1,31 @@ -// var nude = require('../nude'), -// images = [ -// { id: 1, expected: false }, -// { id: 2, expected: false }, -// { id: 3, expected: false }, -// { id: 4, expected: true } -// ]; +var nude = require('../nude'), + images = [ + { id: 1, expected: false }, + { id: 2, expected: false }, + { id: 3, expected: false }, + { id: 4, expected: true } + ], + url = 'https://i.ytimg.com/vi/FLdK7Hatm0w/hqdefault.jpg' -// images.forEach(function(image) { -// nude.scan(__dirname + '/images/' + image.id + '.jpg', function(res) { -// console.log('Image ' + image.id + ': ' + res); -// console.log('Expected: ' + image.expected); -// }); -// }); -var nude = require('../nude') -nude.scan('https://i.ytimg.com/vi/FLdK7Hatm0w/hqdefault.jpg', function(res) { - console.log(res) +images.forEach(function(image) { + nude.scan(__dirname + '/images/' + image.id + '.jpg', function(res) { + console.log('Image ' + image.id + ': ' + res); + console.log('Expected: ' + image.expected); + }); +}); + + +nude.scanUrl(url, function(res) { + console.log('Scan Url:', res) }) + +async function asynchronous() { + var isNudeFromDisk = await nude.scanAsync(__dirname + '/images/' + '4.jpg') + console.log('Async local test: ', isNudeFromDisk) + + var isNudeRemote = await nude.scanUrlAsync(url) + console.log('Async remote test: ', isNudeRemote) +} + + +asynchronous() From 0f979be8a4b20098d439551478fc945c384ccce4 Mon Sep 17 00:00:00 2001 From: Ramin Mousavi Date: Mon, 11 Dec 2017 01:51:22 +0330 Subject: [PATCH 3/3] fix version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cddcf13..8cc213d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "filter", "detect" ], - "version": "0.1", + "version": "1.0.0", "homepage": "https://github.com/chadsmith/node-nude", "repository": { "type": "git",