1 /**
  2  * @author jerome etienne jerome.etienne@gmail.com
  3  * @author Djordje Lukic lukic.djordje@gmail.com
  4 */
  5 
  6 /**
  7  * @namespace
  8 */
  9 var ImgProc	= {};
 10 
 11 /**
 12  * horizontal flip
 13 */
 14 ImgProc.fliph	= function(imageData)
 15 {
 16 	var p	= imageData.data;
 17 	var w	= imageData.width;
 18 	var h	= imageData.height;
 19 	var tmp;
 20 	for(var y = 0; y < h; y++){
 21 		for(var x = 0; x < w/2; x++){
 22 			var i1	= (x		+ y*w)*4;
 23 			var i2	= ((w-1-x)	+ y*w)*4;
 24 			// swap red
 25 			tmp	= p[i1+0];
 26 			p[i1+0]	= p[i2+0];
 27 			p[i2+0]	= tmp;
 28 			// swap red
 29 			tmp	= p[i1+1];
 30 			p[i1+1]	= p[i2+1];
 31 			p[i2+1]	= tmp;
 32 			// swap green
 33 			tmp	= p[i1+2];
 34 			p[i1+2]	= p[i2+2];
 35 			p[i2+2]	= tmp;
 36 			// swap alpha
 37 			tmp	= p[i1+3];
 38 			p[i1+3]	= p[i2+3];
 39 			p[i2+3]	= tmp;
 40 		}
 41 	}
 42 }
 43 
 44 /**
 45  * Convert the image to luminance
 46 */
 47 ImgProc.luminance	= function(imageData, ratio)
 48 {
 49 	ratio	= ratio !== undefined ? ratio : 1.0;
 50 	var p	= imageData.data;
 51 	var w	= imageData.width;
 52 	var h	= imageData.height;
 53 	for(var i = 0, y = 0; y < h; y++){
 54 		for(var x = 0; x < w; x++, i += 4){
 55 			var luminance	= (0.2126*(p[i+0]/255)) + (0.7152*(p[i+1]/255)) + (0.0722*(p[i+2]/255))
 56 			luminance	= Math.floor(luminance*ratio*255);
 57 			p[i+0]	= luminance;
 58 			p[i+1]	= luminance;
 59 			p[i+2]	= luminance;
 60 		}
 61 	}
 62 }
 63 
 64 /**
 65  * Duplicate the ImgData
 66 */
 67 ImgProc.duplicate	= function(srcImgData, ctx)
 68 {
 69 	return ImgProc.copy(srcImgData, ctx.createImageData(srcImgData))
 70 
 71 /** old but tested version
 72 	var dstImgData	= ctx.createImageData(srcImgData);
 73 	var pSrc	= srcImgData.data;
 74 	var pDst	= dstImgData.data;
 75 	for(var i = 0; i < pSrc.length; i++){
 76 		pDst[i]	= pSrc[i];
 77 	}
 78 	return dstImgData;
 79 */
 80 }
 81 
 82 /**
 83  * Copy the ImgData
 84 */
 85 ImgProc.copy	= function(srcImgData, dstImgData)
 86 {
 87 	console.assert(srcImgData.width === dstImgData.width );
 88 	console.assert(srcImgData.height === dstImgData.height );
 89 	var pSrc	= srcImgData.data;
 90 	var pDst	= dstImgData.data;
 91 	for(var i = 0; i < pSrc.length; i++){
 92 		pDst[i]	= pSrc[i];
 93 	}
 94 	return dstImgData;
 95 }
 96 
 97 //////////////////////////////////////////////////////////////////////////////////
 98 //										//
 99 //////////////////////////////////////////////////////////////////////////////////
100 
101 /**
102  * Sobel operator
103  * 
104  * see http://en.wikipedia.org/wiki/Sobel_operator
105 */
106 ImgProc.sobel = function(srcImgProc, dstImgProc)
107 {
108 	var pSrc= srcImgProc.data;
109 	var w	= srcImgProc.width;
110 	var h	= srcImgProc.height;
111 	var pDst= dstImgProc.data;
112 	// TODO make be optimized http://en.wikipedia.org/wiki/Sobel_operator#Technical_details
113 	var sobel_x	= [	[-1,0,1],
114 				[-2,0,2],
115 				[-1,0,1]
116 			];
117 	var sobel_y	= [	[-1,-2,-1],
118 				[ 0, 0, 0],
119 				[ 1, 2, 1]
120 			];
121   
122 	var greyAt = function(p, x, y) {
123 		var offset	= x * 4 + y * w * 4;
124 		return p[offset] * 0.3 + p[offset + 1] * 0.59 + p[offset + 2] * 0.11;
125 	}
126 	// TODO instead of recomputing the index everytime, make it incremental
127 	for(var x = 1; x < w - 2; x++) {
128 		for(var y = 1; y < h - 2; y++) {
129 			var px = (sobel_x[0][0] * greyAt(pSrc, x-1,y-1)) + (sobel_x[0][1] * greyAt(pSrc, x,y-1)) + (sobel_x[0][2] * greyAt(pSrc, x+1,y-1)) +
130 				 (sobel_x[1][0] * greyAt(pSrc, x-1,y))   + (sobel_x[1][1] * greyAt(pSrc, x,y))   + (sobel_x[1][2] * greyAt(pSrc, x+1,y))   +
131 				 (sobel_x[2][0] * greyAt(pSrc, x-1,y+1)) + (sobel_x[2][1] * greyAt(pSrc, x,y+1)) + (sobel_x[2][2] * greyAt(pSrc, x+1,y+1))
132 			
133 			var py = (sobel_y[0][0] * greyAt(pSrc, x-1,y-1)) + (sobel_y[0][1] * greyAt(pSrc, x,y-1)) + (sobel_y[0][2] * greyAt(pSrc, x+1,y-1)) +
134 				 (sobel_y[1][0] * greyAt(pSrc, x-1,y))   + (sobel_y[1][1] * greyAt(pSrc, x,y))   + (sobel_y[1][2] * greyAt(pSrc, x+1,y))   +
135 				 (sobel_y[2][0] * greyAt(pSrc, x-1,y+1)) + (sobel_y[2][1] * greyAt(pSrc, x,y+1)) + (sobel_y[2][2] * greyAt(pSrc, x+1,y+1))
136 	
137 			var val = Math.ceil(Math.sqrt(px * px + py * py));
138 			
139 			var offset	= y * w * 4 + x * 4;
140 			pDst[offset+0]	= val;
141 			pDst[offset+1]	= val;
142 			pDst[offset+2]	= val;
143 		}
144 	}
145 }
146 
147 //////////////////////////////////////////////////////////////////////////////////
148 //										//
149 //////////////////////////////////////////////////////////////////////////////////
150 
151 ImgProc.threshold	= function(imageData, r, g, b)
152 {
153 	var p	= imageData.data;
154 	var w	= imageData.width;
155 	var h	= imageData.height;
156 	for(var i = 0, y = 0; y < h; y++){
157 		for(var x = 0; x < w; x++, i += 4){
158 			if( (p[i+0] >= r.min && p[i+0] <= r.max)
159 					&& (p[i+1] >= g.min && p[i+1] <= g.max)
160 					&& (p[i+2] >= b.min && p[i+2] <= b.max) ){
161 			}else{
162 				p[i+0]	= p[i+1] = p[i+2] = 0;
163 			}
164 		}
165 	}
166 }
167 
168 ImgProc.convert	= function(imageData, callback)
169 {
170 	var p	= imageData.data;
171 	var w	= imageData.width;
172 	var h	= imageData.height;
173 	var i	= 0;
174 	for(var y = 0; y < h; y++){
175 		for(var x = 0; x < w; x++, i += 4){
176 			callback(p, i, x, y, imageData);
177 		}
178 	}
179 }
180 
181 
182 //////////////////////////////////////////////////////////////////////////////////
183 //										//
184 //////////////////////////////////////////////////////////////////////////////////
185 
186 ImgProc.hline	= function(imageData, y, r, g, b, a)
187 {
188 	var p	= imageData.data;
189 	var w	= imageData.width;
190 	var i	= y * w * 4;
191 	r	= r !== undefined ? r : 255;
192 	g	= g !== undefined ? g : 255;
193 	b	= b !== undefined ? b : 255;
194 	a	= a !== undefined ? a : 255;
195 	for(var x = 0; x < w; x++, i += 4){
196 		p[i+0]	= r;
197 		p[i+1]	= g;
198 		p[i+2]	= b;
199 		p[i+3]	= a;
200 	}
201 }
202 
203 ImgProc.vline	= function(imageData, x, r, g, b, a)
204 {
205 	var p	= imageData.data;
206 	var w	= imageData.width;
207 	var h	= imageData.height;
208 	r	= r !== undefined ? r : 255;
209 	g	= g !== undefined ? g : 255;
210 	b	= b !== undefined ? b : 255;
211 	a	= a !== undefined ? a : 255;
212 	for(var i = x*4, y = 0; y < h; y++, i += w*4){
213 		p[i+0]	= r;
214 		p[i+1]	= g;
215 		p[i+2]	= b;
216 		p[i+3]	= a;
217 	}
218 }
219 
220 //////////////////////////////////////////////////////////////////////////////////
221 //										//
222 //////////////////////////////////////////////////////////////////////////////////
223 
224 ImgProc.smoothHistogram	= function(hist, factor)
225 {
226 	var value	= 0;
227 	for(var i = 0; i < hist.length; i++ ){
228 		value	+= (hist[i] - value) * factor;
229 		hist[i]	= value;
230 	}
231 }
232 
233 ImgProc.windowedAverageHistogram	= function(hist, width)
234 {
235 	var halfW	= Math.floor(width/2);
236 	var winSum	= 0;
237 	var winLen	= 0;
238 	var origHist	= new Array(hist.length);
239 	for(var i = 0; i < hist.length; i++)	origHist[i]	= hist[i];
240 
241 	// init window
242 	for(var i = 0; i < halfW; i++){
243 		winSum	+= hist[i];
244 		winLen	++;
245 	}
246 
247 	for(var i = 0; i < hist.length; i++){
248 		// update window forward
249 		if( i + halfW < hist.length ){	
250 			winSum	+= origHist[i+halfW];
251 			winLen	++;
252 		}
253 		// update window backward
254 		if( i-halfW >= 0 ){	
255 			winSum	-= origHist[i-halfW];
256 			winLen	--;
257 		}
258 		// compute the actual value
259 		hist[i]	= winSum/winLen;
260 	}
261 }
262 
263 ImgProc.getMaxHistogram	= function(hist, imageData)
264 {
265 	var max	= -Number.MAX_VALUE;
266 	var idx	= 0;
267 	for(var i = 0; i < hist.length; i++ ){
268 		if( hist[i] > max ){
269 			max	= hist[i];
270 			idx	= i;
271 		}
272 	}
273 	return {max: max, idx: idx}
274 }
275 
276 ImgProc.filterHistogram	= function(hist, filterFn)
277 {
278 	for(var i = 0; i < hist.length; i++ ){
279 		var val	= hist[i];
280 		filterFn(val, i, hist);
281 	}
282 }
283 
284 //////////////////////////////////////////////////////////////////////////////////
285 //										//
286 //////////////////////////////////////////////////////////////////////////////////
287 
288 ImgProc.computeHorizontalHistogram	= function(imageData, filter)
289 {
290 	var p	= imageData.data;
291 	var w	= imageData.width;
292 	var h	= imageData.height;
293 	var hist= new Float64Array(w);
294 	console.assert(filter, "invalid parameter")
295 	for(var x = 0; x < w; x++){
296 		for(var i = x*4, y = 0; y < h; y++, i += w*4){
297 			if( filter(p, i, x, y, imageData) )	hist[x]++;
298 		}
299 	}
300 	return hist;
301 }
302 
303 ImgProc.displayHorizontalHistogram	= function(imageData, hist)
304 {
305 	var p	= imageData.data;
306 	var w	= imageData.width;
307 	var h	= imageData.height;
308 	console.assert(hist.length === w);
309 	// create temporary cancas
310 	var canvas	= document.createElement('canvas');
311 	canvas.width	= w;
312 	canvas.height	= h;
313 	var ctx		= canvas.getContext("2d");
314 	// display the histogram in the canvas
315 	ctx.fillStyle	= 'red';
316 	for(var x = 0; x < w; x++ ){
317 		ctx.fillRect(x, h-hist[x], 1, hist[x]);
318 	}
319 	// copy the canvas in imageData
320 	var srcImgData	= ctx.getImageData(0,0, canvas.width, canvas.height);
321 	var pSrc	= srcImgData.data;
322 	var pDst	= imageData.data;
323 	for(var i = 0; i < pSrc.length; i += 4){
324 		if( pSrc[i+0] !== 0 || pSrc[i+1] !== 0 || pSrc[i+2] !== 0 ){
325 			pDst[i+0]	= pSrc[i+0];
326 			pDst[i+1]	= pSrc[i+1];
327 			pDst[i+2]	= pSrc[i+2];
328 		}
329 		pDst[i+3]	= 255;
330 	}
331 }
332 
333 
334 
335 //////////////////////////////////////////////////////////////////////////////////
336 //										//
337 //////////////////////////////////////////////////////////////////////////////////
338 
339 ImgProc.computeVerticalHistogram	= function(imageData, filter)
340 {
341 	var p	= imageData.data;
342 	var w	= imageData.width;
343 	var h	= imageData.height;
344 	var hist= new Float64Array(h);
345 	console.assert(filter, "invalid parameter")
346 	for(var i = 0, y = 0; y < h; y++){
347 		for(var x = 0; x < w; x++, i += 4){
348 			if( filter(p, i, x, y, imageData) )	hist[y]++;
349 		}
350 	}
351 	return hist;
352 }
353 
354 ImgProc.displayVerticalHistogram	= function(imageData, hist)
355 {
356 	var p	= imageData.data;
357 	var w	= imageData.width;
358 	var h	= imageData.height;
359 	console.assert(hist.length === h);
360 	// create temporary cancas
361 	var canvas	= document.createElement('canvas');
362 	canvas.width	= w;
363 	canvas.height	= h;
364 	var ctx		= canvas.getContext("2d");
365 	// display the histogram in the canvas
366 	ctx.fillStyle	= 'red';
367 	for(var y = 0; y < h; y++ ){
368 		ctx.fillRect(0, y, hist[y], 1);
369 	}
370 	// copy the canvas in imageData
371 	var srcImgData	= ctx.getImageData(0,0, canvas.width, canvas.height);
372 	var pSrc	= srcImgData.data;
373 	var pDst	= imageData.data;
374 	for(var i = 0; i < pSrc.length; i += 4){
375 		if( pSrc[i+0] !== 0 || pSrc[i+1] !== 0 || pSrc[i+2] !== 0 ){
376 			pDst[i+0]	= pSrc[i+0];
377 			pDst[i+1]	= pSrc[i+1];
378 			pDst[i+2]	= pSrc[i+2];
379 		}
380 		pDst[i+3]	= 255;
381 	}
382 }
383 
384 
385 //////////////////////////////////////////////////////////////////////////////////
386 //										//
387 //////////////////////////////////////////////////////////////////////////////////
388 
389 ImgProc.computeColorHistogram	= function(imageData)
390 {
391 	var p	= imageData.data;
392 	var w	= imageData.width;
393 	var h	= imageData.height;
394 	var hist= {
395 		r	: new Float64Array(256),
396 		g	: new Float64Array(256),
397 		b	: new Float64Array(256)
398 	};
399 	
400 	for(var i = 0, y = 0; y < h; y++){
401 		for(var x = 0; x < w; x++, i += 4){
402 			hist.r[ p[i+0] ]++;
403 			hist.g[ p[i+1] ]++;
404 			hist.b[ p[i+2] ]++;
405 		}
406 	}
407 	return hist;
408 }
409 
410 ImgProc.normalizeColorHistogram	= function(colorHistogram)
411 {
412 	var hist= colorHistogram;
413 
414 	// get the max value
415 	var max	= -Number.MAX_VALUE;
416 	for (var i = 0; i < 256; i++ ){
417 		max	= Math.max(max, hist.r[i]);
418 		max	= Math.max(max, hist.g[i]);
419 		max	= Math.max(max, hist.b[i]);
420 	}
421 	// normalize the histogram
422 	for (var i = 0; i < 256; i++ ){
423 		hist.r[i]	/= max;
424 		hist.g[i]	/= max;
425 		hist.b[i]	/= max;
426 	}
427 
428 	var sumR	= 0;
429 	var sumG	= 0;
430 	var sumB	= 0;
431 	for (var i = 0; i < 256; i++ ){
432 		sumR	+= hist.r[i];
433 		sumG	+= hist.g[i];
434 		sumB	+= hist.b[i];
435 	}
436 	console.log("sum", max, sumR, sumG, sumB)
437 }
438 
439 ImgProc.displayColorHistogram	= function(imageData, colorHistogram)
440 {
441 	var p	= imageData.data;
442 	var w	= imageData.width;
443 	var h	= imageData.height;
444 	var hist= colorHistogram;
445 
446 	var canvas	= document.createElement('canvas');
447 	canvas.width	= w;
448 	canvas.height	= h;
449 	var ctx		= canvas.getContext("2d");
450 
451 
452 	var barW	= canvas.width / 256;
453 	var barH	= canvas.height / 3;
454 
455 	ctx.fillStyle	= 'red';
456 	var barYOffset	= 0 * barH;
457 	for (var i = 0; i < 256; i++ ){
458 		var valH	= Math.floor(hist.r[i]*barH);
459 		ctx.fillRect(i*barW, barYOffset+(barH-valH), barW, valH);
460 	}
461 
462 	ctx.fillStyle	= 'green';
463 	var barYOffset	= 1 * barH;
464 	for (var i = 0; i < 256; i++ ){
465 		var valH	= Math.floor(hist.g[i]*barH);
466 		ctx.fillRect(i*barW, barYOffset+(barH-valH), barW, valH);
467 	}
468 
469 	ctx.fillStyle	= 'blue';
470 	var barYOffset	= 2 * barH;
471 	for (var i = 0; i < 256; i++ ){
472 		var valH	= Math.floor(hist.b[i]*barH);
473 		ctx.fillRect(i*barW, barYOffset+(barH-valH), barW, valH);
474 	}
475 
476 	var srcImgData	= ctx.getImageData(0,0, canvas.width, canvas.height);
477 	var pSrc	= srcImgData.data;
478 	var pDst	= imageData.data;
479 	for(var i = 0; i < pSrc.length; i += 4){
480 		if( pSrc[i+0] !== 0 || pSrc[i+1] !== 0 || pSrc[i+2] !== 0 ){
481 			pDst[i+0]	= pSrc[i+0];
482 			pDst[i+1]	= pSrc[i+1];
483 			pDst[i+2]	= pSrc[i+2];
484 		}
485 	}
486 };
487