Made 'Past' links open in a new window
[imgurfusker.git] / worker.nude.js
blob2e0c21877b39811ad138b054854b2ee8bce4010b
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 var skinRegions = [],
9 skinMap = [],
10 canvas = {};
12 onmessage = function(event){
13 canvas.width = event.data[1];
14 canvas.height = event.data[2];
15 scanImage(event.data[0]);
20 Array.prototype.remove = function(index) {
21 var rest = this.slice(index + 1);
22 this.length = index;
23 return this.push.apply(this, rest);
26 function scanImage(imageData){
28 var detectedRegions = [],
29 mergeRegions = [],
30 width = canvas.width,
31 lastFrom = -1,
32 lastTo = -1;
35 var addMerge = function(from, to){
36 lastFrom = from;
37 lastTo = to;
38 var len = mergeRegions.length,
39 fromIndex = -1,
40 toIndex = -1;
43 while(len--){
45 var region = mergeRegions[len],
46 rlen = region.length;
48 while(rlen--){
50 if(region[rlen] == from){
51 fromIndex = len;
54 if(region[rlen] == to){
55 toIndex = len;
62 if(fromIndex != -1 && toIndex != -1 && fromIndex == toIndex){
63 return;
66 if(fromIndex == -1 && toIndex == -1){
68 mergeRegions.push([from, to]);
70 return;
72 if(fromIndex != -1 && toIndex == -1){
74 mergeRegions[fromIndex].push(to);
75 return;
77 if(fromIndex == -1 && toIndex != -1){
78 mergeRegions[toIndex].push(from);
79 return;
81 if(fromIndex != -1 && toIndex != -1 && fromIndex != toIndex){
82 mergeRegions[fromIndex] = mergeRegions[fromIndex].concat(mergeRegions[toIndex]);
83 mergeRegions.remove(toIndex);
84 return;
89 // iterate the image from the top left to the bottom right
90 var length = imageData.length,
91 width = canvas.width;
93 for(var i = 0, u = 1; i < length; i+=4, u++){
95 var r = imageData[i],
96 g = imageData[i+1],
97 b = imageData[i+2],
98 x = (u>width)?((u%width)-1):u,
99 y = (u>width)?(Math.ceil(u/width)-1):1;
101 if(classifySkin(r, g, b)){ //
102 skinMap.push({"id": u, "skin": true, "region": 0, "x": x, "y": y, "checked": false});
104 var region = -1,
105 checkIndexes = [u-2, (u-width)-2, u-width-1, (u-width)],
106 checker = false;
108 for(var o = 0; o < 4; o++){
109 var index = checkIndexes[o];
110 if(skinMap[index] && skinMap[index].skin){
111 if(skinMap[index].region!=region && region!=-1 && lastFrom!=region && lastTo!=skinMap[index].region){
112 addMerge(region, skinMap[index].region);
114 region = skinMap[index].region;
115 checker = true;
119 if(!checker){
120 skinMap[u-1].region = detectedRegions.length;
121 detectedRegions.push([skinMap[u-1]]);
122 continue;
123 }else{
125 if(region > -1){
127 if(!detectedRegions[region]){
128 detectedRegions[region] = [];
131 skinMap[u-1].region = region;
132 detectedRegions[region].push(skinMap[u-1]);
137 }else{
138 skinMap.push({"id": u, "skin": false, "region": 0, "x": x, "y": y, "checked": false});
143 merge(detectedRegions, mergeRegions);
144 analyseRegions();
147 // function for merging detected regions
148 function merge(detectedRegions, mergeRegions){
150 var length = mergeRegions.length,
151 detRegions = [];
154 // merging detected regions
155 while(length--){
157 var region = mergeRegions[length],
158 rlen = region.length;
160 if(!detRegions[length])
161 detRegions[length] = [];
163 while(rlen--){
164 var index = region[rlen];
165 detRegions[length] = detRegions[length].concat(detectedRegions[index]);
166 detectedRegions[index] = [];
171 // push the rest of the regions to the detRegions array
172 // (regions without merging)
173 var l = detectedRegions.length;
174 while(l--){
175 if(detectedRegions[l].length > 0){
176 detRegions.push(detectedRegions[l]);
180 // clean up
181 clearRegions(detRegions);
185 // clean up function
186 // only pushes regions which are bigger than a specific amount to the final result
187 function clearRegions(detectedRegions){
189 var length = detectedRegions.length;
191 for(var i=0; i < length; i++){
192 if(detectedRegions[i].length > 30){
193 skinRegions.push(detectedRegions[i]);
199 function analyseRegions(){
201 // sort the detected regions by size
202 var length = skinRegions.length,
203 totalPixels = canvas.width * canvas.height,
204 totalSkin = 0;
206 // if there are less than 3 regions
207 if(length < 3){
208 postMessage(false);
209 return;
212 // sort the skinRegions with bubble sort algorithm
213 (function(){
214 var sorted = false;
215 while(!sorted){
216 sorted = true;
217 for(var i = 0; i < length-1; i++){
218 if(skinRegions[i].length < skinRegions[i+1].length){
219 sorted = false;
220 var temp = skinRegions[i];
221 skinRegions[i] = skinRegions[i+1];
222 skinRegions[i+1] = temp;
226 })();
228 // count total skin pixels
229 while(length--){
230 totalSkin += skinRegions[length].length;
233 // check if there are more than 15% skin pixel in the image
234 if((totalSkin/totalPixels)*100 < 15){
235 // if the percentage lower than 15, it's not nude!
236 //console.log("it's not nude :) - total skin percent is "+((totalSkin/totalPixels)*100)+"% ");
237 postMessage(false);
238 return;
242 // check if the largest skin region is less than 35% of the total skin count
243 // AND if the second largest region is less than 30% of the total skin count
244 // AND if the third largest region is less than 30% of the total skin count
245 if((skinRegions[0].length/totalSkin)*100 < 35
246 && (skinRegions[1].length/totalSkin)*100 < 30
247 && (skinRegions[2].length/totalSkin)*100 < 30){
248 // the image is not nude.
249 //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)+"%");
250 postMessage(false);
251 return;
255 // check if the number of skin pixels in the largest region is less than 45% of the total skin count
256 if((skinRegions[0].length/totalSkin)*100 < 45){
257 // it's not nude
258 //console.log("it's not nude :) - the biggest region contains less than 45%: "+((skinRegions[0].length/totalSkin)*100)+"%");
259 postMessage(false);
260 return;
263 // TODO:
264 // build the bounding polygon by the regions edge values:
265 // Identify the leftmost, the uppermost, the rightmost, and the lowermost skin pixels of the three largest skin regions.
266 // Use these points as the corner points of a bounding polygon.
268 // TODO:
269 // check if the total skin count is less than 30% of the total number of pixels
270 // AND the number of skin pixels within the bounding polygon is less than 55% of the size of the polygon
271 // if this condition is true, it's not nude.
273 // TODO: include bounding polygon functionality
274 // if there are more than 60 skin regions and the average intensity within the polygon is less than 0.25
275 // the image is not nude
276 if(skinRegions.length > 60){
277 //console.log("it's not nude :) - more than 60 skin regions");
278 postMessage(false);
279 return;
283 // otherwise it is nude
284 postMessage(true);
287 function classifySkin(r, g, b){
288 // A Survey on Pixel-Based Skin Color Detection Techniques
289 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)),
290 nurgb = toNormalizedRgb(r, g, b),
291 nr = nurgb[0],
292 ng = nurgb[1],
293 nb = nurgb[2],
294 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)),
295 //hsv = toHsv(r, g, b),
296 //h = hsv[0]*100,
297 //s = hsv[1],
298 //hsvClassifier = (h < 50 && h > 0 && s > 0.23 && s < 0.68);
299 hsv = toHsvTest(r, g, b),
300 h = hsv[0],
301 s = hsv[1],
302 hsvClassifier = (h > 0 && h < 35 && s > 0.23 && s < 0.68);
304 * ycc doesnt work
306 ycc = toYcc(r, g, b),
307 y = ycc[0],
308 cb = ycc[1],
309 cr = ycc[2],
310 yccClassifier = ((y > 80) && (cb > 77 && cb < 127) && (cr > 133 && cr < 173));
313 return (rgbClassifier || normRgbClassifier || hsvClassifier); //
315 function toYcc(r, g, b){
316 r/=255,g/=255,b/=255;
317 var y = 0.299*r + 0.587*g + 0.114*b,
318 cr = r - y,
319 cb = b - y;
321 return [y, cr, cb];
324 function toHsv(r, g, b){
325 return [
326 // hue
327 Math.acos((0.5*((r-g)+(r-b)))/(Math.sqrt((Math.pow((r-g),2)+((r-b)*(g-b)))))),
328 // saturation
329 1-(3*((Math.min(r,g,b))/(r+g+b))),
330 // value
331 (1/3)*(r+g+b)
334 function toHsvTest(r, g, b){
335 var h = 0,
336 mx = Math.max(r, g, b),
337 mn = Math.min(r, g, b),
338 dif = mx - mn;
340 if(mx == r){
341 h = (g - b)/dif;
342 }else if(mx == g){
343 h = 2+((g - r)/dif)
344 }else{
345 h = 4+((r - g)/dif);
347 h = h*60;
348 if(h < 0){
349 h = h+360;
352 return [h, 1-(3*((Math.min(r,g,b))/(r+g+b))),(1/3)*(r+g+b)] ;
355 function toNormalizedRgb(r, g, b){
356 var sum = r+g+b;
357 return [(r/sum), (g/sum), (b/sum)];