addendum to prior change for github compatibility
[face-privacy-filter.git] / web_demo / demo-framework.js
1 /*
2   ===============LICENSE_START=======================================================
3   Acumos Apache-2.0
4   ===================================================================================
5   Copyright (C) 2017-2018 AT&T Intellectual Property & Tech Mahindra. All rights reserved.
6   ===================================================================================
7   This Acumos software file is distributed by AT&T and Tech Mahindra
8   under the Apache License, Version 2.0 (the "License");
9   you may not use this file except in compliance with the License.
10   You may obtain a copy of the License at
11
12   http://www.apache.org/licenses/LICENSE-2.0
13
14   This file is distributed on an "AS IS" BASIS,
15   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   See the License for the specific language governing permissions and
17   limitations under the License.
18   ===============LICENSE_END=========================================================
19 */
20 /**
21  face-privacy.js - send frames to an face privacy service; clone from image-classes.js
22
23  Videos or camera are displayed locally and frames are periodically sent to GPU image-net classifier service (developed by Zhu Liu) via http post.
24  For webRTC, See: https://gist.github.com/greenido/6238800
25
26  D. Gibbon 6/3/15
27  D. Gibbon 4/19/17 updated to new getUserMedia api, https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
28  D. Gibbon 8/1/17 adapted for system
29  E. Zavesky 10/19/17 adapted for video+image
30  E. Zavesky 05/05/18 adapted for row-based image and other results
31  E. Zavesky 05/30/18 adapted for single image input, github preview, safe posting (forked from model-specific code)
32  E. Zavesky 07/11/18 allow proto type grouping, proto text file download, binary chunk upload (as proto)
33  */
34
35
36 /**
37  * main entry point
38  */
39 function demo_init(objSetting) {
40     if (!objSetting) objSetting = {};
41
42     // clone/extend the default input from our main script
43     $(document.body).data('hdparams', $.extend(true, objSetting, {      // store global vars in the body element
44         classificationServer: getUrlParameter('url-image'), // default to what's in our url prameter
45         protoObj: null,   // to be back-filled after protobuf load {'root':obj, 'methods':{'xx':{'typeIn':x, 'typeOut':y}} }
46         protoPayloadInput: null,   //payload for encoded message download (if desired)
47         protoPayloadOutput: null,   //payload for encoded message download (if desired)
48         protoKeys: null,  // currently selected protobuf method (if any)
49         frameCounter: 0,
50         totalFrames: 900000,    // stop after this many frames just to avoid sending frames forever if someone leaves page up
51         frameInterval: 500,             // Milliseconds to sleep between sending frames to reduce server load and reduce results updates
52         frameTimer: -1,         // frame clock for processing
53         maxSrcVideoWidth: 512,  // maximum image width for processing
54         serverIsLocal: true,    // server is local versus 'firewall' version
55         imageIsWaiting: false,  // blocking to prevent too many queued frames
56
57         // functional customizations for each demo
58         documentTitle: "Protobuf Demo",
59         mediaList: [],        //relative URLs of media files
60         protoList: [],        //relative URLs of proto files to include
61         domHeaders: { "Content-type": "text/plain;charset=UTF-8" },   //defaults for headers
62         // TODO: should be binary ideally, domHeaders: { "Content-type": "application/octet-stream;charset=UTF-8" },   //defaults for headers
63
64         // Objects from DOM elements
65         video: document.getElementById('srcVideo'),
66         srcImgCanvas: document.getElementById('srcImgCanvas'),  // we have a 'src' source image
67     }));
68
69     var hd = $(document.body).data('hdparams');
70     if (hd.video) {
71         hd.video.addEventListener("loadedmetadata", newVideo);
72     }
73
74     $("#protoSource").prop("disabled",true).click(downloadBlobProto);
75     $("#protoInput").prop("disabled",true).click(downloadBlobIn);
76     $("#protoOutput").prop("disabled",true).click(downloadBlobOut);
77     $("#resultText").hide();
78     $("#protoBinary").change(doPostBinaryFile);
79
80
81     //add text input tweak
82     $("#serverUrl").change(function() {
83         $(document.body).data('hdparams')['classificationServer'] = $(this).val();
84         updateLink("serverLink");
85     }).val($(document.body).data('hdparams')['classificationServer'])
86     //set launch link at first
87     $("#protoMethod").change(function() {
88         updateProto($(this).attr('id'));
89     });
90
91     //if protobuf is enabled, fire load event for it as well
92     hd.protoObj = {};  //clear from last load
93     $.each(hd.protoList, function(idx, protoTuple) {     //load relevant protobuf tuples
94         protobuf_load.apply(this, protoTuple);      //load each file independently
95         $("#protoSource").prop("disabled",false);
96     });
97
98     // add buttons to change video
99     $.each(hd.mediaList, function(key) {
100         //TODO: integrarte as DIV instead of button
101         var button = $('<button/>').text(videos[key].name).attr('movie', videos[key].url);
102         $("#sourceRibbon").append(button);
103     });
104
105     // add buttons to change video or image
106         $("#sourceRibbon").children("div,button").click(function() {
107         var $this = $(this);
108         $this.siblings().removeClass('selected'); //clear other selection
109         $this.addClass('selected');
110
111         var movieAttr = $this.attr('movie');
112         var objImg = $this.children('img')[0];
113         if (objImg) {
114             movieAttr = $(objImg).attr('movie');
115             objImg = $(objImg);
116         }
117
118         clearInterval(hd.frameTimer);   // stop the processing
119         hd.video.pause();
120
121         if (movieAttr) {
122             switchVideo(movieAttr);
123         }
124         else {
125             $(hd.video).hide();
126             $(srcImgCanvas).show();
127             if (objImg)
128                 switchImage(objImg.attr('src'));
129         }
130         }).first().click();
131 }
132
133
134 function protobuf_load(pathProto, forceSelect) {
135     protobuf.load(pathProto, function(err, root) {
136         if (err) {
137             console.log("[protobuf]: Error!: "+err);
138             throw err;
139         }
140         var domSelect = $("#protoMethod");
141         var numMethods = domSelect.children().length;
142         $.each(root.nested, function(namePackage, objPackage) {    // walk all
143             if ('Model' in objPackage && 'methods' in objPackage.Model) {    // walk to model and functions...
144                 var typeSummary = {'root':root, 'methods':{}, 'path':pathProto };
145                 var fileBase = pathProto.split(/[\\\/]/);       // added 7/11/18 for proto context
146                 fileBase = fileBase[fileBase.length - 1];
147                 var domGroup = $("<optgroup label='"+fileBase+" - "+namePackage+"' >");
148                 $.each(objPackage.Model.methods, function(nameMethod, objMethod) {  // walk methods
149                     typeSummary['methods'][nameMethod] = {};
150                     typeSummary['methods'][nameMethod]['typeIn'] = namePackage+'.'+objMethod.requestType;
151                     typeSummary['methods'][nameMethod]['typeOut'] = namePackage+'.'+objMethod.responseType;
152                     typeSummary['methods'][nameMethod]['service'] = namePackage+'.'+nameMethod;
153
154                     //create HTML object as well
155                     var namePretty = namePackage+"."+nameMethod;
156                     var domOpt = $("<option />").attr("value", namePretty).text(
157                         nameMethod+ " (input: "+objMethod.requestType
158                         +", output: "+objMethod.responseType+")");
159                     if (numMethods==0) {    // first method discovery
160                         domSelect.append($("<option />").attr("value","").text("(disabled, not loaded)")); //add 'disabled'
161                     }
162                     if (forceSelect) {
163                         domOpt.attr("selected", 1);
164                     }
165                     domGroup.append(domOpt);
166                     numMethods++;
167                 });
168                 domSelect.append(domGroup);
169                 $(document.body).data('hdparams').protoObj[namePackage] = typeSummary;   //save new method set
170                 $("#protoContainer").show();
171             }
172         });
173         console.log("[protobuf]: Load successful, found "+numMethods+" model methods.");
174     });
175 }
176
177 // modify the link and update our url
178 function updateLink(domId) {
179     var newServer = $(document.body).data('hdparams')['classificationServer'];
180     var sNewUrl = updateQueryStringParameter(window.location.href, "url-image", newServer, "?");
181     $("#"+domId).attr('href', sNewUrl);
182     $("#serverUrl").val(newServer);
183     //window.history.pushState({}, $(document.body).data('hdparams')['documentTitle'], sNewUrl);
184 }
185
186 // update proto link
187 function updateProto(domProtoCombo) {
188     var nameProtoMethod = $("#"+domProtoCombo+" option:selected").attr('value');
189     $(document.body).data('hdparams').protoKeys = null;
190     if (nameProtoMethod && nameProtoMethod.length) {     //valid protobuf type?
191         var partsURL = $(document.body).data('hdparams').classificationServer.split("/");
192         var protoKeys = nameProtoMethod.split(".", 2);       //modified for multiple detect/pixelate models
193         $(document.body).data('hdparams').protoKeys = protoKeys;
194         partsURL[partsURL.length-1] = protoKeys[1];
195         $(document.body).data('hdparams').classificationServer = partsURL.join("/");   //rejoin with new endpoint
196         updateLink("serverLink", $(document.body).data('hdparams').classificationServer);
197     }
198 }
199
200 // https://stackoverflow.com/a/6021027
201 function updateQueryStringParameter(uri, key, value, separator) {
202     var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
203     if (uri.match(re)) {
204         return uri.replace(re, '$1' + key + "=" + value + '$2');
205     }
206     if (!separator) //allow forced/override
207        separator = uri.indexOf('?') !== -1 ? "&" : "?";
208     return uri + separator + key + "=" + value;
209 }
210
211 // https://stackoverflow.com/a/3354511
212 window.onpopstate = function(e){
213     if(e.state){
214         //document.getElementById("content").innerHTML = e.state.html;
215         $(document.body).data('hdparams')['documentTitle'] = e.state.pageTitle;
216     }
217 };
218
219 function getUrlParameter(key) {
220     var re = new RegExp("([?&])" + key + "=(.*?)(&|$)", "i");
221     var match = window.location.search.match(re)
222     if (match) {
223         //console.log(match);
224         return match[match.length-2];
225     }
226 };
227
228 /**
229  * Change the video source and restart
230  */
231 function switchVideo(movieAttr) {
232         var hd = $(document.body).data('hdparams');
233
234     // Set the video source based on URL specified in the 'videos' list, or select camera input
235     $(hd.video).show();
236     $(srcImgCanvas).hide();
237     if (movieAttr == "Camera") {
238         var constraints = {audio: false, video: true};
239         navigator.mediaDevices.getUserMedia(constraints)
240             .then(function(mediaStream) {
241                 hd.video.srcObject = mediaStream;
242                 hd.video.play();
243             })
244             .catch(function(err) {
245                 console.log(err.name + ": " + err.message);
246             });
247     } else {
248         var mp4 = document.getElementById("mp4");
249         mp4.setAttribute("src", movieAttr);
250         hd.video.load();
251         hd.video.autoplay = true;
252         newVideo();
253     }
254 }
255
256 /**
257  * Called after a new video has loaded (at least the video metadata has loaded)
258  */
259 function newVideo() {
260         var hd = $(document.body).data('hdparams');
261         hd.frameCounter = 0;
262         hd.imageIsWaiting = false;
263
264         // set processing canvas size based on source video
265         var pwidth = hd.video.videoWidth;
266         var pheight = hd.video.videoHeight;
267         if (pwidth > hd.maxSrcVideoWidth) {
268                 pwidth = hd.maxSrcVideoWidth;
269                 pheight = Math.floor((pwidth / hd.video.videoWidth) * pheight); // preserve aspect ratio
270         }
271         hd.srcImgCanvas.width = pwidth;
272         hd.srcImgCanvas.height = pheight;
273
274     updateProto("protoMethod");
275     hd.frameTimer = setInterval(nextFrame, hd.frameInterval); // start the processing
276 }
277
278
279 /**
280  * process the next video frame
281  */
282 function nextFrame() {
283         var hd = $(document.body).data('hdparams');
284         if (hd.video.ended || hd.video.paused) {
285                 return;
286         }
287     switchImage(hd.video, true);
288 }
289
290 function switchImage(imgSrc, isVideo) {
291     var canvas = $(document.body).data('hdparams')['srcImgCanvas'];
292     if (!isVideo) {
293         var img = new Image();
294         img.crossOrigin = "Anonymous";
295         img.onload = function () {
296             var ctx = canvas.getContext('2d');
297             var canvasCopy = document.createElement("canvas");
298             var copyContext = canvasCopy.getContext("2d");
299
300             var ratio = 1;
301
302             //console.log( $(document.body).data('hdparams'));
303             //console.log( [ img.width, img.height]);
304             // https://stackoverflow.com/a/2412606
305             if(img.width > $(document.body).data('hdparams')['canvasMaxW'])
306                 ratio = $(document.body).data('hdparams')['canvasMaxW'] / img.width;
307             if(ratio*img.height > $(document.body).data('hdparams')['canvasMaxH'])
308                 ratio = $(document.body).data('hdparams')['canvasMaxH'] / img.height;
309
310             canvasCopy.width = img.width;
311             canvasCopy.height = img.height;
312             copyContext.drawImage(img, 0, 0);
313
314             canvas.width = img.width * ratio;
315             canvas.height = img.height * ratio;
316             ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
317             //document.removeChild(canvasCopy);
318             updateProto("protoMethod");
319             doPostImage(canvas, '#resultsDiv', '#destImg', canvas.toDataURL());
320         }
321         img.src = imgSrc;  //copy source, let image load
322     }
323     else if (!$(document.body).data('hdparams').imageIsWaiting) {
324         var ctx = canvas.getContext('2d');
325         var canvasCopy = document.createElement("canvas");
326         var copyContext = canvasCopy.getContext("2d");
327         var ratio = 1;
328
329         if(imgSrc.videoWidth > $(document.body).data('hdparams')['canvasMaxW'])
330             ratio = $(document.body).data('hdparams')['canvasMaxW'] / imgSrc.videoWidth;
331         if(ratio*imgSrc.videoHeight > $(document.body).data('hdparams')['canvasMaxH'])
332             ratio = $(document.body).data('hdparams')['canvasMaxH'] / canvasCopy.height;
333
334         //console.log("Canvas Copy:"+canvasCopy.width+"/"+canvasCopy.height);
335         //console.log("Canvas Ratio:"+ratio);
336         //console.log("Video: "+imgSrc.videoWidth+"x"+imgSrc.videoHeight);
337         canvasCopy.width = imgSrc.videoWidth;     //large as possible
338         canvasCopy.height = imgSrc.videoHeight;
339         copyContext.drawImage(imgSrc, 0, 0);
340
341         canvas.width = canvasCopy.width * ratio;
342         canvas.height = canvasCopy.height * ratio;
343         ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
344         //document.removeChild(canvasCopy);
345         doPostImage(canvas, '#resultsDiv', '#destImg', canvas.toDataURL());
346     }
347 }
348
349
350 /**
351  * post an image from the canvas to the service
352  */
353 function doPostImage(srcCanvas, dstDiv, dstImg, imgPlaceholder) {
354     var dataURL = srcCanvas.toDataURL('image/jpeg', 1.0);
355     var hd = $(document.body).data('hdparams');
356     var sendPayload = null;
357
358     hd.imageIsWaiting = true;
359     var domHeaders = {};
360
361     //console.log("[doPostImage]: Selected method ... '"+typeInput+"'");
362     if (hd.protoKeys) {     //valid protobuf type?
363         var blob = dataURItoBlob(dataURL, true);
364         domHeaders = $.extend({}, hd.domHeaders);       //rewrite with defaults
365
366         // fields from .proto file at time of writing...
367         // message Image {
368         //   string mime_type = 1;
369         //   bytes image_binary = 2;
370         // }
371
372         //TODO: should we always assume this is input? answer: for now, YES, always image input!
373         var inputPayload = { "mimeType": blob.type, "imageBinary": blob.bytes };
374
375         // ---- method for processing from a type ----
376         var msgInput = hd.protoObj[hd.protoKeys[0]]['root'].lookupType(hd.protoObj[hd.protoKeys[0]]['methods'][hd.protoKeys[1]]['typeIn']);
377         // Verify the payload if necessary (i.e. when possibly incomplete or invalid)
378         var errMsg = msgInput.verify(inputPayload);
379         if (errMsg) {
380             var strErr = "[doPostImage]: Error during type verify for object input into protobuf method. ("+errMsg+")";
381             $(dstDiv).empty().html(strErr);
382             console.log(strErr);
383             throw Error(strErr);
384         }
385         // Create a new message
386         var msgTransmit = msgInput.create(inputPayload);
387         // Encode a message to an Uint8Array (browser) or Buffer (node)
388         sendPayload = msgInput.encode(msgTransmit).finish();
389
390         //downloadBlob(sendPayload, 'protobuf.bin', 'application/octet-stream');
391         // NOTE: TO TEST THIS BINARY BLOB, use some command-line magic like this...
392         //  protoc --decode=mMJuVapnmIbrHlZGKyuuPDXsrkzpGqcr.FaceImage model.proto < protobuf.bin
393         $("#protoInput").prop("disabled",false);
394         hd.protoPayloadInput = sendPayload;
395
396         //request.responseType = 'arraybuffer';
397     }
398     else if (hd.protoList.length) {
399         var strErr = "[doPostImage]: Proto method expected but unavailable in POST, aborting send.";
400         console.log(strErr);
401         throw Error(strErr);
402     }
403     else {
404         var blob = dataURItoBlob(dataURL, false);
405         sendPayload = new FormData();
406         if (hd.serverIsLocal) {
407             serviceURL = hd.classificationServer;
408             sendPayload.append("image_binary", blob);
409             sendPayload.append("mime_type", blob.type);
410         }
411         else {      //disabled now for direct URL specification
412             serviceURL = hd.classificationServerFirewall;
413             sendPayload.append("myFile", blob);
414             sendPayload.append("rtnformat", "json");
415             sendPayload.append("myList", "5");  // limit the number of classes (max 1000)
416         }
417     }
418     doPostPayload(sendPayload, domHeaders, dstDiv, dstImg, imgPlaceholder);
419 }
420
421
422 function doPostBinaryFile(e)  {
423     // https://stackoverflow.com/a/10811427
424     // https://stackoverflow.com/a/17512132
425     var fileReader = new FileReader();
426     fileReader.onload = function(e) {
427         console.log("[doPostBinaryFile]: Sending uploaded binary file of length "+e.target.result.byteLength);
428         doPostBlob(e.target.result);
429     };
430     var fileLocal = $(this)[0].files[0];
431     fileReader.readAsArrayBuffer(fileLocal);
432
433     //usage: $("#ingredient_file").change(doPostBinaryFile);
434 }
435
436 function doPostBlob(blobData)
437 {
438     doPostPayload(blobData, null, '#resultsDiv', '#destImg', null);
439 }
440
441
442 function doPostPayload(sendPayload, domHeaders, dstDiv, dstImg, imgPlaceholder)
443 {
444     var hd = $(document.body).data('hdparams');
445     hd.imageIsWaiting = true;
446
447     dstDiv = $(dstDiv);
448     $("#postSpinner").remove();     //erase previously existing one
449     dstDiv.append($("<div id='postSpinner' class='spinner'>&nbsp;</div>"));
450     if (dstImg)     //convert to jquery dom object
451         dstImg = $(dstImg);
452     if (!domHeaders)    //pull existing headers from config
453         domHeaders = $(document.body).data('hdparams').domHeaders;
454
455     //$(dstImg).addClaas('workingImage').siblings('.spinner').remove().after($("<span class='spinner'>&nbsp;</span>"));
456     $.ajax({
457         type: 'POST',
458         url: hd.classificationServer,
459         data: sendPayload,
460         crossDomain: true,
461         dataType: 'native',
462         xhrFields: {
463             responseType: 'arraybuffer'
464         },
465         processData: false,
466         headers: domHeaders,
467         error: function (data, textStatus, errorThrown) {
468             //console.log(textStatus);
469             if (textStatus=="error") {
470                 textStatus += " (Was the transform URL valid? Was the right method selected?) ";
471             }
472             var errStr = "Error: Failed javascript POST (err: "+textStatus+","+errorThrown+")";
473             console.log(errStr);
474             dstDiv.html(errStr);
475             hd.imageIsWaiting = false;
476             return false;
477         },
478         success: function(data, textStatus, jqXHR) {
479             // what do we do with a good processing result?
480             //
481             //  data: the raw body from the response
482             //  dstImg: the dom element of a destination image
483             //  methodKeys: which protomethod was selected
484             //  dstImg: the dom element of a destination image (if available)
485             //  imgPlaceholder: the exported canvas image from last source
486             //
487             var returnState = processResult(data, dstDiv, hd.protoKeys, dstImg, imgPlaceholder);
488             hd.imageIsWaiting = false;
489             return returnState;
490         }
491         });
492 }
493
494 /**
495  * convert base64/URLEncoded data component to raw binary data held in a string
496  *
497  * Stoive, http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata
498  */
499 function dataURItoBlob(dataURI, wantBytes) {
500     // convert base64/URLEncoded data component to raw binary data held in a string
501     var byteString;
502     if (dataURI.split(',')[0].indexOf('base64') >= 0)
503         byteString = atob(dataURI.split(',')[1]);
504     else
505         byteString = unescape(dataURI.split(',')[1]);
506
507     // separate out the mime component
508     var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
509
510     // write the bytes of the string to a typed array
511     var ia = new Uint8Array(byteString.length);
512     for (var i = 0; i < byteString.length; i++) {
513         ia[i] = byteString.charCodeAt(i);
514     }
515     //added for returning bytes directly
516     if (wantBytes) {
517         return {'bytes':ia, 'type':mimeString};
518     }
519     return new Blob([ia], {type:mimeString});
520 }
521
522 // https://stackoverflow.com/a/12713326
523 function Uint8ToString(u8a){
524   var CHUNK_SZ = 0x8000;
525   var c = [];
526   for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
527     c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
528   }
529   return c.join("");
530 }
531
532 function BlobToDataURI(data, mime) {
533     var b64encoded = btoa(Uint8ToString(data));
534     return "data:"+mime+";base64,"+b64encoded;
535 }
536
537 // ----- diagnostic tool to download binary blobs ----
538 function downloadBlobOut() {
539     return downloadBlob($(document.body).data('hdparams').protoPayloadOutput, "protobuf.out.bin");
540 }
541
542 function downloadBlobIn() {
543     return downloadBlob($(document.body).data('hdparams').protoPayloadInput, "protobuf.in.bin");
544 }
545
546 function downloadBlobProto() {
547     var namePackage = $(document.body).data('hdparams').protoKeys[0];
548     var pathProto = $(document.body).data('hdparams').protoObj[namePackage]['path'];
549     protobuf.util.fetch(pathProto, {binary:true}, function(statusStr, data) {
550         var fileBase = pathProto.split(/[\\\/]/);       // added 7/11/18 for proto context
551         fileBase = fileBase[fileBase.length - 1];
552         return downloadBlob(data, fileBase, "text/plain");
553     });
554 }
555
556
557 //  https://stackoverflow.com/a/33622881
558 function downloadBlob(data, fileName, mimeType) {
559     //if there is no data, filename, or mime provided, make our own
560     if (!data)
561         data = $(document.body).data('hdparams').protoPayloadInput;
562     if (!fileName)
563         fileName = "protobuf.bin";
564     if (!mimeType)
565         mimeType = "application/octet-stream";
566
567     var blob, url;
568     blob = new Blob([data], {
569         type: mimeType
570     });
571     url = window.URL.createObjectURL(blob);
572     downloadURL(url, fileName, mimeType);
573     setTimeout(function() {
574         return window.URL.revokeObjectURL(url);
575     }, 1000);
576 };
577
578 function downloadURL(data, fileName) {
579     var a;
580     a = document.createElement('a');
581     a.href = data;
582     a.download = fileName;
583     document.body.appendChild(a);
584     a.style = 'display: none';
585     a.click();
586     a.remove();
587 };
588
589
590 //load image that has been uploaded into a canvas
591 function handleImage(e){
592     var reader = new FileReader();
593     reader.onload = function(event){
594         switchImage(event.target.result);
595     }
596     reader.readAsDataURL(e.target.files[0]);
597 }
598
599