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..3cce0d3 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; @@ -36,146 +37,194 @@ var classifySkin = function(r, g, b) { return [r / sum, g / sum, b / sum]; }; -module.exports = { - scan: function(src, callback) { - fs.readFile(src, function(err, data) { - if(err) - throw err; - 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 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) + }) +} + +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/package.json b/package.json index 7d29f5e..8cc213d 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,12 @@ "author": "Chad Smith (http://twitter.com/chadsmith)", "name": "nude", "description": "Nudity detection for Node.js", - "keywords": ["images", "filter", "detect"], - "version": "0.1", + "keywords": [ + "images", + "filter", + "detect" + ], + "version": "1.0.0", "homepage": "https://github.com/chadsmith/node-nude", "repository": { "type": "git", @@ -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..83a9108 100644 --- a/readme.md +++ b/readme.md @@ -7,9 +7,18 @@ Node.js port of [nude.js](https://github.com/pa7/nude.js) by [Patrick Wied](http `npm install nude` ## Usage overview +``` + var nude = require('nude'); - var nude = require('nude'); - - nude.scan(__dirname + '/images/photo.jpg', function(res) { - console.log('Contains nudity: ' + res); - }); + nude.scan('', function(res) { + console.log('Contains nudity: ' + res); + }); + + var isNude = await nude.scanAsync(''); + + nude.scanUrl('', function(res) { + console.log('Contains nudity: ' + res); + }); + + var isNude = await nude.scanUrlAsync(''); +``` diff --git a/tests/test.js b/tests/test.js index b3576e4..16c883b 100644 --- a/tests/test.js +++ b/tests/test.js @@ -4,7 +4,8 @@ var nude = require('../nude'), { 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) { @@ -12,3 +13,19 @@ images.forEach(function(image) { 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()