- minor addition to the web demo writeup
[face-privacy-filter.git] / web_demo / face-privacy.js
1 /**\r
2  image-classes.js - send frames to an image classification service\r
3 \r
4  Videos or camera are displayed locally and frames are periodically sent to GPU image-net classifier service (developed by Zhu Liu) via http post.\r
5  For webRTC, See: https://gist.github.com/greenido/6238800\r
6  \r
7  D. Gibbon 6/3/15\r
8  D. Gibbon 4/19/17 updated to new getUserMedia api, https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\r
9  D. Gibbon 8/1/17 adapted for Cognita\r
10  */\r
11  \r
12 "use strict";\r
13 \r
14 /**\r
15  * main entry point\r
16  */\r
17 $(document).ready(function() {\r
18     var urlDefault = getUrlParameter('url-image');\r
19     if (!urlDefault)\r
20         urlDefault = "http://localhost:8884/transform";\r
21 \r
22         $(document.body).data('hdparams', {     // store global vars in the body element\r
23                 classificationServer: urlDefault,\r
24                 frameCounter: 0,\r
25                 frameInterval: 500,             // Milliseconds to sleep between sending frames to reduce server load and reduce results updates\r
26                 frameTimer: -1,         // frame clock for processing\r
27                 imageIsWaiting: false,  // blocking to prevent too many queued frames\r
28                 // Objects from DOM elements\r
29                 srcImgCanvas: document.getElementById('srcImgCanvas'),  // we have a 'src' source image\r
30                 destImg: document.getElementById('destImg'),    // we have a 'src' source image\r
31                 video: document.getElementById('srcVideo'),\r
32         });\r
33     $(document.body).data('hdparams')['canvasMaxH'] = $(document.body).data('hdparams')['srcImgCanvas'].height;\r
34     $(document.body).data('hdparams')['canvasMaxW'] = $(document.body).data('hdparams')['srcImgCanvas'].width;\r
35 \r
36         //add text input tweak\r
37         $("#serverUrl").change(function() {\r
38             $(document.body).data('hdparams')['classificationServer'] = $(this).val();\r
39         updateLink("serverLink");\r
40         }).val($(document.body).data('hdparams')['classificationServer'])\r
41         //set launch link at first\r
42     updateLink("serverLink");\r
43 \r
44         // add buttons to change video\r
45         $("#sourceRibbon div").click(function() {\r
46             var $this = $(this);\r
47             $this.siblings().removeClass('selected'); //clear other selection\r
48             $this.addClass('selected');\r
49             var objImg = $this.children('img')[0];\r
50             var hd = $(document.body).data('hdparams');\r
51             if (objImg) {\r
52                 switchImage(objImg.src);\r
53             clearInterval(hd.frameTimer);       // stop the processing\r
54 \r
55             var movieAttr = $(objImg).attr('movie');\r
56             if (movieAttr) {\r
57                 // Set the video source based on URL specified in the 'videos' list, or select camera input\r
58                 $(hd.video).show();\r
59                 $(srcImgCanvas).hide();\r
60                 if (movieAttr == "Camera") {\r
61                     var constraints = {audio: false, video: true};\r
62                     navigator.mediaDevices.getUserMedia(constraints)\r
63                         .then(function(mediaStream) {\r
64                             hd.video.srcObject = mediaStream;\r
65                             hd.video.play();\r
66                         })\r
67                         .catch(function(err) {\r
68                             console.log(err.name + ": " + err.message);\r
69                         });\r
70                 } else {\r
71                     var mp4 = document.getElementById("mp4");\r
72                     mp4.setAttribute("src", movieAttr);\r
73                     hd.video.load();\r
74                     newVideo();\r
75                 }\r
76             }\r
77             else {\r
78                 hd.video.pause();\r
79                 $(hd.video).hide();\r
80                 $(srcImgCanvas).show();\r
81             }\r
82             }\r
83         });\r
84 \r
85         //allow user-uploaded images\r
86     var imageLoader = document.getElementById('imageLoader');\r
87     imageLoader.addEventListener('change', handleImage, false);\r
88 \r
89     //trigger first click\r
90     $("#sourceRibbon div")[0].click();\r
91 });\r
92 \r
93 \r
94 /**\r
95  * Called after a new video has loaded (at least the video metadata has loaded)\r
96  */\r
97 function newVideo() {\r
98         var hd = $(document.body).data('hdparams');\r
99         hd.frameCounter = 0;\r
100         hd.imageIsWaiting = false;\r
101         hd.video.play();\r
102 \r
103         // set processing canvas size based on source video\r
104         var pwidth = hd.video.videoWidth;\r
105         var pheight = hd.video.videoHeight;\r
106         if (pwidth > hd.maxSrcVideoWidth) {\r
107                 pwidth = hd.maxSrcVideoWidth;\r
108                 pheight = Math.floor((pwidth / hd.video.videoWidth) * pheight); // preserve aspect ratio\r
109         }\r
110         hd.srcImgCanvas.width = pwidth;\r
111         hd.srcImgCanvas.height = pheight;\r
112 \r
113         hd.frameTimer = setInterval(nextFrame, hd.frameInterval); // start the processing\r
114 }\r
115 \r
116 /**\r
117  * process the next video frame\r
118  */\r
119 function nextFrame() {\r
120         var hd = $(document.body).data('hdparams');\r
121         if (hd.video.ended || hd.video.paused) {\r
122                 return;\r
123         }\r
124     switchImage(hd.video, true);\r
125 }\r
126 \r
127 function updateLink(domId) {\r
128     var sPageURL = decodeURIComponent(window.location.search.split('?')[0]);\r
129     var newServer = $(document.body).data('hdparams')['classificationServer'];\r
130     var sNewUrl = sPageURL+"?url-image="+newServer;\r
131     $("#"+domId).attr('href', sNewUrl);\r
132 }\r
133 \r
134 function switchImage(imgSrc, isVideo) {\r
135     var canvas = $(document.body).data('hdparams')['srcImgCanvas'];\r
136     if (!isVideo) {\r
137         var img = new Image();\r
138         img.onload = function () {\r
139             var ctx = canvas.getContext('2d');\r
140             var canvasCopy = document.createElement("canvas");\r
141             var copyContext = canvasCopy.getContext("2d");\r
142 \r
143             var ratio = 1;\r
144 \r
145             //console.log( $(document.body).data('hdparams'));\r
146             //console.log( [ img.width, img.height]);\r
147             // https://stackoverflow.com/a/2412606\r
148             if(img.width > $(document.body).data('hdparams')['canvasMaxW'])\r
149                 ratio = $(document.body).data('hdparams')['canvasMaxW'] / img.width;\r
150             if(ratio*img.height > $(document.body).data('hdparams')['canvasMaxH'])\r
151                 ratio = $(document.body).data('hdparams')['canvasMaxH'] / img.height;\r
152 \r
153             canvasCopy.width = img.width;\r
154             canvasCopy.height = img.height;\r
155             copyContext.drawImage(img, 0, 0);\r
156 \r
157             canvas.width = img.width * ratio;\r
158             canvas.height = img.height * ratio;\r
159             ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);\r
160             //document.removeChild(canvasCopy);\r
161             doPostImage(canvas, '#destImg', canvas.toDataURL());\r
162         }\r
163         img.src = imgSrc;  //copy source, let image load\r
164     }\r
165     else if (!$(document.body).data('hdparams').imageIsWaiting) {\r
166         var ctx = canvas.getContext('2d');\r
167         var canvasCopy = document.createElement("canvas");\r
168         var copyContext = canvasCopy.getContext("2d");\r
169         var ratio = 1;\r
170 \r
171         if(imgSrc.videoWidth > $(document.body).data('hdparams')['canvasMaxW'])\r
172             ratio = $(document.body).data('hdparams')['canvasMaxW'] / imgSrc.videoWidth;\r
173         if(ratio*imgSrc.videoHeight > $(document.body).data('hdparams')['canvasMaxH'])\r
174             ratio = $(document.body).data('hdparams')['canvasMaxH'] / canvasCopy.height;\r
175 \r
176         //console.log("Canvas Copy:"+canvasCopy.width+"/"+canvasCopy.height);\r
177         //console.log("Canvas Ratio:"+ratio);\r
178         //console.log("Video: "+imgSrc.videoWidth+"x"+imgSrc.videoHeight);\r
179         canvasCopy.width = imgSrc.videoWidth;     //large as possible\r
180         canvasCopy.height = imgSrc.videoHeight;\r
181         copyContext.drawImage(imgSrc, 0, 0);\r
182 \r
183         canvas.width = canvasCopy.width * ratio;\r
184         canvas.height = canvasCopy.height * ratio;\r
185         ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);\r
186         //document.removeChild(canvasCopy);\r
187         doPostImage(canvas, '#destImg', canvas.toDataURL());\r
188     }\r
189 }\r
190 \r
191 \r
192 //load image that has been uploaded into a vancas\r
193 function handleImage(e){\r
194     var reader = new FileReader();\r
195     reader.onload = function(event){\r
196         switchImage(event.target.result);\r
197     }\r
198     reader.readAsDataURL(e.target.files[0]);\r
199 }\r
200 \r
201 \r
202 \r
203 // https://stackoverflow.com/questions/19491336/get-url-parameter-jquery-or-how-to-get-query-string-values-in-js\r
204 function getUrlParameter(sParam) {\r
205     var sPageURL = decodeURIComponent(window.location.search.substring(1)),\r
206         sURLVariables = sPageURL.split('&'),\r
207         sParameterName,\r
208         i;\r
209 \r
210     for (i = 0; i < sURLVariables.length; i++) {\r
211         sParameterName = sURLVariables[i].split('=');\r
212 \r
213         if (sParameterName[0] === sParam) {\r
214             return sParameterName[1] === undefined ? true : sParameterName[1];\r
215         }\r
216     }\r
217 };\r
218 \r
219 \r
220 /**\r
221  * post an image from the canvas to the service\r
222  */\r
223 function doPostImage(srcCanvas, dstImg, dataPlaceholder) {\r
224         var serviceURL = "";\r
225         var dataURL = srcCanvas.toDataURL('image/jpeg', 1.0);\r
226         var blob = dataURItoBlob(dataURL);\r
227         var hd = $(document.body).data('hdparams');\r
228         var fd = new FormData();\r
229 \r
230         $(document.body).data('hdparams').imageIsWaiting = true;\r
231     serviceURL = hd.classificationServer;\r
232     fd.append("base64_data", blob);\r
233     fd.append("mime_type", "image/jpeg");\r
234     var $dstImg = $(dstImg);\r
235     if ($dstImg.attr('src')=='') {\r
236         $dstImg.attr('src', dataPlaceholder);\r
237         //$(dstImg).addClass('workingImage').attr('src', dataPlaceholder);\r
238     }\r
239     //$(dstImg).addClaas('workingImage').siblings('.spinner').remove().after($("<span class='spinner'>&nbsp;</span>"));\r
240 \r
241         var request = new XMLHttpRequest();\r
242         hd.imageIsWaiting = true;\r
243         request.onreadystatechange=function() {\r
244                 if (request.readyState==4 && request.status==200) {\r
245                     var responseJson = $.parseJSON(request.responseText);\r
246                     var respImage = responseJson[0];\r
247                     // https://stackoverflow.com/questions/21227078/convert-base64-to-image-in-javascript-jquery\r
248             $dstImg.attr('src', "data:"+respImage['mime_type']+";base64,"+respImage['base64_data']).removeClass('workingImage');\r
249                         //genClassTable($.parseJSON(request.responseText), dstDiv);\r
250                         hd.imageIsWaiting = false;\r
251                 }\r
252         }\r
253         request.open("POST", serviceURL, true);\r
254         request.send(fd);\r
255         $(document.body).data('hdparams').imageIsWaiting = false;\r
256 }\r
257 \r
258 \r
259 /**\r
260  * convert base64/URLEncoded data component to raw binary data held in a string\r
261  *\r
262  * Stoive, http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata\r
263  */\r
264 function dataURItoBlob(dataURI) {\r
265     // convert base64/URLEncoded data component to raw binary data held in a string\r
266     var byteString;\r
267     if (dataURI.split(',')[0].indexOf('base64') >= 0)\r
268         byteString = atob(dataURI.split(',')[1]);\r
269     else\r
270         byteString = unescape(dataURI.split(',')[1]);\r
271 \r
272     // separate out the mime component\r
273     var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];\r
274 \r
275     // write the bytes of the string to a typed array\r
276     var ia = new Uint8Array(byteString.length);\r
277     for (var i = 0; i < byteString.length; i++) {\r
278         ia[i] = byteString.charCodeAt(i);\r
279     }\r
280 \r
281     return new Blob([ia], {type:mimeString});\r
282 }\r
283 \r