Made 'Past' links open in a new window
[imgurfusker.git] / noworker.nude.js
blobaa64cea5c3006fccecc9073868f49888b8a28210
1 /*
2 * Nude.js - Nudity detection with Javascript and HTMLCanvas
3 *
4 * Author: Patrick Wied ( http://www.patrick-wied.at )
5 * Version: 0.1 (2010-11-21)
6 * License: MIT License
7 */
8 (function(){
9 Array.prototype.remove = function(index) {
10 var rest = this.slice(index + 1);
11 this.length = index;
12 return this.push.apply(this, rest);
15 var nude = (function(){
16 // private var definition
17 var canvas = null,
18 ctx = null,
19 skinRegions = [],
20 resultFn = null,
21 img = null,
22 // private functions
23 initCanvas = function(){
24 canvas = document.createElement("canvas");
25 // the canvas should not be visible
26 canvas.style.display = "none";
27 var b = document.getElementsByTagName("body")[0];
28 b.appendChild(canvas);
29 ctx = canvas.getContext("2d");
31 loadImageById = function(id){
32 // get the image
33 var img = document.getElementById(id);
34 // apply the width and height to the canvas element
35 canvas.width = img.width;
36 canvas.height = img.height;
37 // reset the result function
38 resultFn = null;
39 // draw the image into the canvas element
40 ctx.drawImage(img, 0, 0);
43 loadImageByElement = function(element){
44 // apply width and height to the canvas element
45 // make sure you set width and height at the element
46 canvas.width = element.width;
47 canvas.height = element.height;
48 // reset result function
49 resultFn = null;
50 // draw the image/video element into the canvas
51 ctx.drawImage(element, 0, 0);
53 scanImage = function(){
54 // get the image data
55 var image = ctx.getImageData(0, 0, canvas.width, canvas.height),
56 imageData = image.data,
57 skinMap = [],
58 detectedRegions = [],
59 mergeRegions = [],
60 width = canvas.width,
61 lastFrom = -1,
62 lastTo = -1;
65 var addMerge = function(from, to){
66 lastFrom = from;
67 lastTo = to;
68 var len = mergeRegions.length,
69 fromIndex = -1,
70 toIndex = -1;
73 while(len--){
75 var region = mergeRegions[len],
76 rlen = region.length;
78 while(rlen--){
80 if(region[rlen] == from){
81 fromIndex = len;
84 if(region[rlen] == to){
85 toIndex = len;
92 if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex){
93 return;
96 if(fromIndex == -1 && toIndex == -1){
98 mergeRegions.push([from, to]);
100 return;
102 if(fromIndex != -1 && toIndex == -1){
104 mergeRegions[fromIndex].push(to);
105 return;
107 if(fromIndex == -1 && toIndex != -1){
108 mergeRegions[toIndex].push(from);
109 return;
112 if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex){
113 mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]);
114 mergeRegions.remove(toIndex);
115 return;
120 // iterate the image from the top left to the bottom right
121 var length = imageData.length,
122 width = canvas.width;
124 for(var i = 0, u = 1; i < length; i+=4, u++){
126 var r = imageData[i],
127 g = imageData[i+1],
128 b = imageData[i+2],
129 x = (u>width)?((u%width)-1):u,
130 y = (u>width)?(Math.ceil(u/width)-1):1;
132 if(classifySkin(r, g, b)){ //
133 skinMap.push({"id": u, "skin": true, "region": 0, "x": x, "y": y, "checked": false});
135 var region = -1,
136 checkIndexes = [u-2, (u-width)-2, u-width-1, (u-width)],
137 checker = false;
139 for(var o = 0; o < 4; o++){
140 var index = checkIndexes[o];
141 if(skinMap[index] && skinMap[index].skin){
142 if(skinMap[index].region!=region && region!=-1 && lastFrom!=region && lastTo!=skinMap[index].region){
143 addMerge(region, skinMap[index].region);
145 region = skinMap[index].region;
146 checker = true;
150 if(!checker){
151 skinMap[u-1].region = detectedRegions.length;
152 detectedRegions.push([skinMap[u-1]]);
153 continue;
154 }else{
156 if(region > -1){
158 if(!detectedRegions[region]){
159 detectedRegions[region] = [];
162 skinMap[u-1].region = region;
163 detectedRegions[region].push(skinMap[u-1]);
168 }else{
169 skinMap.push({"id": u, "skin": false, "region": 0, "x": x, "y": y, "checked": false});
174 merge(detectedRegions, mergeRegions);
175 analyseRegions();
177 // function for merging detected regions
178 merge = function(detectedRegions, mergeRegions){
180 var length = mergeRegions.length,
181 detRegions = [];
184 // merging detected regions
185 while(length--){
187 var region = mergeRegions[length],
188 rlen = region.length;
190 if(!detRegions[length])
191 detRegions[length] = [];
193 while(rlen--){
194 var index = region[rlen];
195 detRegions[length] = detRegions[length].concat(detectedRegions[index]);
196 detectedRegions[index] = [];
201 // push the rest of the regions to the detRegions array
202 // (regions without merging)
203 var l = detectedRegions.length;
204 while(l--){
205 if(detectedRegions[l].length > 0){
206 detRegions.push(detectedRegions[l]);
210 // clean up
211 clearRegions(detRegions);
214 // clean up function
215 // only pushes regions which are bigger than a specific amount to the final result
216 clearRegions = function(detectedRegions){
218 var length = detectedRegions.length;
220 for(var i=0; i < length; i++){
221 if(detectedRegions[i].length > 30){
222 skinRegions.push(detectedRegions[i]);
227 analyseRegions = function(){
229 // sort the detected regions by size
230 var length = skinRegions.length,
231 totalPixels = canvas.width * canvas.height,
232 totalSkin = 0;
234 // if there are less than 3 regions
235 if(length < 3){
236 resultHandler(false);
237 return;
240 // sort the skinRegions with bubble sort algorithm
241 (function(){
242 var sorted = false;
243 while(!sorted){
244 sorted = true;
245 for(var i = 0; i < length-1; i++){
246 if(skinRegions[i].length < skinRegions[i+1].length){
247 sorted = false;
248 var temp = skinRegions[i];
249 skinRegions[i] = skinRegions[i+1];
250 skinRegions[i+1] = temp;
254 })();
256 // count total skin pixels
257 while(length--){
258 totalSkin += skinRegions[length].length;
261 // check if there are more than 15% skin pixel in the image
262 if((totalSkin/totalPixels)*100 < 15){
263 // if the percentage lower than 15, it's not nude!
264 //console.log("it's not nude :) - total skin percent is "+((totalSkin/totalPixels)*100)+"% ");
265 resultHandler(false);
266 return;
270 // check if the largest skin region is less than 35% of the total skin count
271 // AND if the second largest region is less than 30% of the total skin count
272 // AND if the third largest region is less than 30% of the total skin count
273 if((skinRegions[0].length/totalSkin)*100 < 35
274 && (skinRegions[1].length/totalSkin)*100 < 30
275 && (skinRegions[2].length/totalSkin)*100 < 30){
276 // the image is not nude.
277 //console.log("it's not nude :) - less than 35%,30%,30% skin in the biggest areas :" + ((skinRegions[0].length/totalSkin)*100) + "%, " + ((skinRegions[1].length/totalSkin)*100)+"%, "+((skinRegions[2].length/totalSkin)*100)+"%");
278 resultHandler(false);
279 return;
283 // check if the number of skin pixels in the largest region is less than 45% of the total skin count
284 if((skinRegions[0].length/totalSkin)*100 < 45){
285 // it's not nude
286 //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
287 resultHandler(false);
288 return;
291 // TODO:
292 // build the bounding polygon by the regions edge values:
293 // Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
294 // Use these points as the corner points of a bounding polygon.
296 // TODO:
297 // check if the total skin count is less than 30% of the total number of pixels
298 // AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
299 // if this condition is true, it's not nude.
301 // TODO: include bounding polygon functionality
302 // if there are more than 60 skin regions and the average intensity within the polygon is less than 0.25
303 // the image is not nude
304 if(skinRegions.length > 60){
305 //console.log("it's not nude :) - more than 60 skin regions");
306 resultHandler(false);
307 return;
311 // otherwise it is nude
312 resultHandler(true);
315 // the result handler will be executed when the analysing process is done
316 // the result contains true (it is nude) or false (it is not nude)
317 // if the user passed an result function to the scan function, the result function will be executed
318 // otherwise the default resulthandling executes
319 resultHandler = function(result){
321 if(resultFn){
322 resultFn(result);
323 }else{
324 if(result)
325 console.log("the picture contains nudity");
329 // colorizeRegions function is for testdevelopment only
330 // the detected skinRegions will be painted in random colors (one color per region)
331 colorizeRegions = function(){
333 var length = skinRegions.length;
334 for(var i = 0; i < length; i++){
336 var region = skinRegions[i],
337 regionLength = region.length,
338 randR = Math.ceil(Math.random()*255),
339 randG = Math.ceil(Math.random()*255),
340 rangB = Math.ceil(Math.random()*255);
342 for(var o = 0; o < regionLength; o++){
344 var pixel = ctx.getImageData(region[o].x, region[o].y, 1,1),
345 pdata = pixel.data;
347 pdata[0] = randR;
348 pdata[1] = randG;
349 pdata[2] = rangB;
351 pixel.data = pdata;
353 ctx.putImageData(pixel, region[o].x, region[o].y);
360 classifySkin = function(r, g, b){
361 // A Survey on Pixel-Based Skin Color Detection Techniques
362 var rgbClassifier = ((r>95) && (g>40 && g <100) && (b>20) && ((Math.max(r,g,b) - Math.min(r,g,b)) > 15) && (Math.abs(r-g)>15) && (r > g) && (r > b)),
363 nurgb = toNormalizedRgb(r, g, b),
364 nr = nurgb[0],
365 ng = nurgb[1],
366 nb = nurgb[2],
367 normRgbClassifier = (((nr/ng)>1.185) && (((r*b)/(Math.pow(r+g+b,2))) > 0.107) && (((r*g)/(Math.pow(r+g+b,2))) > 0.112)),
368 //hsv = toHsv(r, g, b),
369 //h = hsv[0]*100,
370 //s = hsv[1],
371 //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
372 hsv = toHsvTest(r, g, b),
373 h = hsv[0],
374 s = hsv[1],
375 hsvClassifier = (h > 0 && h < 35 && s > 0.23 && s < 0.68);
377 * ycc doesnt work
379 ycc = toYcc(r, g, b),
380 y = ycc[0],
381 cb = ycc[1],
382 cr = ycc[2],
383 yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
386 return (rgbClassifier || normRgbClassifier || hsvClassifier); //
388 toYcc = function(r, g, b){
389 r/=255,g/=255,b/=255;
390 var y = 0.299*r + 0.587*g + 0.114*b,
391 cr = r - y,
392 cb = b - y;
394 return [y, cr, cb];
396 toHsv = function(r, g, b){
397 return [
398 // hue
399 Math.acos((0.5*((r-g)+(r-b)))/(Math.sqrt((Math.pow((r-g),2)+((r-b)*(g-b)))))),
400 // saturation
401 1-(3*((Math.min(r,g,b))/(r+g+b))),
402 // value
403 (1/3)*(r+g+b)
406 toHsvTest = function(r, g, b){
407 var h = 0,
408 mx = Math.max(r, g, b),
409 mn = Math.min(r, g, b),
410 dif = mx - mn;
412 if(mx == r){
413 h = (g - b)/dif;
414 }else if(mx == g){
415 h = 2+((g - r)/dif)
416 }else{
417 h = 4+((r - g)/dif);
419 h = h*60;
420 if(h < 0){
421 h = h+360;
424 return [h, 1-(3*((Math.min(r,g,b))/(r+g+b))),(1/3)*(r+g+b)] ;
427 toNormalizedRgb = function(r, g, b){
428 var sum = r+g+b;
429 return [(r/sum), (g/sum), (b/sum)];
432 // public interface
433 return {
434 init: function(){
435 initCanvas();
437 load: function(param){
438 if(typeof(param) == "string"){
439 loadImageById(param);
440 }else{
441 loadImageByElement(param);
444 scan: function(fn){
445 if(arguments.length>0 && typeof(arguments[0]) == "function"){
446 resultFn = fn;
448 scanImage();
451 })();
452 // register nude at window object
453 window.nude = nude;
454 nude.init();
455 })();