From ee738ede25fef37910f3ff5053f8c8c844fc8dd0 Mon Sep 17 00:00:00 2001 From: r7rohan Date: Fri, 11 Jun 2021 15:12:09 +0530 Subject: [PATCH 01/12] smartpen_mile1 Menu, Both modes, undo align To do: Better heuristics --- apps/viewer/enhance.js | 246 ---------- apps/viewer/uicallbacks.js | 109 +++-- apps/viewer/viewer.html | 9 +- common/enhance.js | 437 ++++++++++++++++++ common/smartpen/autoalign.css | 56 +++ common/smartpen/autoalign.js | 251 ++++++++++ .../openseadragon-canvas-draw-overlay.js | 105 ++++- 7 files changed, 899 insertions(+), 314 deletions(-) delete mode 100644 apps/viewer/enhance.js create mode 100644 common/enhance.js create mode 100644 common/smartpen/autoalign.css create mode 100644 common/smartpen/autoalign.js diff --git a/apps/viewer/enhance.js b/apps/viewer/enhance.js deleted file mode 100644 index c2c5fe91d..000000000 --- a/apps/viewer/enhance.js +++ /dev/null @@ -1,246 +0,0 @@ - -/** Histogram equalization CLAHE - *@param {ImageData} imgData - *@param {int} tiles - *@param {float} clip - *@return {ImageData} imgData -**/ -function clahe(imgData, tiles=32, clip=0.1) { - var tilesize = [tiles, tiles]; - var clipLimit = clip; - - var min=255; var max=0; - var intens=[]; - var levels=[]; - var j=0; - - for (var i=0; i50) { - imgData.data[indexToFill-3]= Math.floor(imgData.data[indexToFill-3]*chn); - imgData.data[indexToFill-2] = Math.floor(imgData.data[indexToFill-2]*chn); - imgData.data[indexToFill-1] = Math.floor(imgData.data[indexToFill-1]*chn); - // imgData.data[indexToFill] = 255 - result; - } - } - return imgData; -} - - -/** Edge detect - *@param {ImageData} imageData - *@param {float} threshold - *@param {int} startx - *@param {int} starty - *@param {int} sizex - *@param {int} sizey - *@return {ImageData} imageData -**/ -function edgedetect(imgData, threshold, is, js, sx, sy) { - var kernelx = - [[-1, 0, 1], - [-2, 0, 2], - [-1, 0, 1]]; - var kernely = - [[-1, -2, -1], - [0, 0, 0], - [1, 2, 1]]; - - is = Math.floor(is); js = Math.floor(js); - sx = Math.floor(sx); sy = Math.floor(sy); - var newdata = new Array(4*sx*sy); - var imgx = applyfilter(imgData, [kernelx, kernelx, kernelx], is, js, sx, sy); - console.log('imgx'); - var imgy = applyfilter(imgData, [kernely, kernely, kernely], is, js, sx, sy); - console.log('imgy'); - - for (var i=is; ithreshold? 0:255; - ox[0] = sum; ox[1] = sum; ox[2]=sum; - setpix(newdata, j-js, i-is, sx, ox); - } - } - return new ImageData(new Uint8ClampedArray(newdata), sx, sy); -} -/** *********************************************************************************************************************/ -/** Apply Filter -*@param {ImageData} imageData -*@param {list} kernel -*@param {int} startx -*@param {int} starty -*@param {int} sizex -*@param {int} sizey - *@return {Array} Data -**/ -function applyfilter(imgData, kernel, is, js, sx, sy) { - var kernelSizey = kernel[0].length; - var kernelSizex = kernel[0][0].length; - var kernelSizey2 = Math.floor(kernelSizey/2); - var kernelSizex2 = Math.floor(kernelSizex/2); - var newdata = new Array(4*sx*sy); - var data = imgData.data; - var width = imgData.width; var height = imgData.height; - - var sumkr=0; var sumkg=0; var sumkb=0; - for (var x = 0; x < kernelSizex; x++) { - for (var y = 0; y < kernelSizey; y++) { - sumkr+=(kernel[0][y][x]); - sumkg+=(kernel[1][y][x]); - sumkb+=(kernel[2][y][x]); - } - } - if (!sumkr)sumkr=1; if (!sumkg)sumkg=1; if (!sumkb)sumkb=1; - - var x1=is-kernelSizex2; var x2=is+sx-kernelSizex2; - var y1=js-kernelSizey2; var y2=js+sy-kernelSizey2; - for (var i=x1; i= width || j+y>=height || i+x<0 || j+y<0) continue; - var px = getpix(data, j+y, i+x, width); - sumr += (px[0])*kernel[0][y][x]; - sumg += (px[1])*kernel[1][y][x]; - sumb += (px[2])*kernel[2][y][x]; - } - } - var col = getpix(data, j+kernelSizey2, i+kernelSizex2, width); - col[0] = Math.floor(sumr/sumkr); - col[1] = Math.floor(sumg/sumkg); - col[2] = Math.floor(sumb/sumkb); - setpix(newdata, j-js+kernelSizey2, i-is+kernelSizex2, sx, col); - } - } - return newdata; -} -function getpix(data, i, j, offset) { - var p = i*offset+j; - return [data[4*p], data[4*p+1], data[4*p+2], data[4*p+3]]; -} -function setpix(data, i, j, offset, newcol) { - var p = i*offset+j; - for (var i = 0; iclipLimit) { - excess = excess + hist[i] - clipLimit; - hist[i] = clipLimit; - } - } - var addExcess = excess/numBins; - var cumuhist = []; - cumuhist[0] = hist[0] + addExcess; - for (var i=1; i=w && y=h && x=h && x>=w) { - idx = (h * w) - 1; - } else { - idx = (y * w + x); - } - var val = Math.floor(intens[idx]); - hist[val]++; - } - } - return hist; -} diff --git a/apps/viewer/uicallbacks.js b/apps/viewer/uicallbacks.js index 9997eca74..59df09d51 100644 --- a/apps/viewer/uicallbacks.js +++ b/apps/viewer/uicallbacks.js @@ -335,7 +335,10 @@ function annotationOn(state, target) { li.appendChild(label); switch (state) { case 1: - $UI.annotOptPanel._action_.style.display = 'none'; + spen.menu(65,0.2); + spen.undo.onclick=()=>canvasDraw.__align_undo(); + //open menu + $UI.annotOptPanel._action_.style.display = ''; label.style.transform = 'translateY(-12px) translateX(18px)'; label.textContent = '1'; label.style.color = ''; @@ -382,6 +385,8 @@ function annotationOff() { ) { saveAnnotation(); } else { + spen.close(); + //close menu canvasDraw.clear(); canvasDraw.drawOff(); $CAMIC.drawContextmenu.off(); @@ -1772,7 +1777,7 @@ function stopDrawing(e) { state === 1 && $CAMIC.viewer.canvasDrawInstance._draws_data_.length > 0 ) { - saveAnnotation(); + $CAMIC.viewer.canvasDrawInstance.isOn = false; } } } @@ -1975,6 +1980,8 @@ function presetLabelOn(label) { return; } + spen.menu(65,0.2); + //open menu canvasDraw.drawMode = label.mode; if (label.mode == 'grid') { canvasDraw.size = [parseInt(label.size), parseInt(label.size)]; @@ -1989,6 +1996,8 @@ function presetLabelOn(label) { function presetLabelOff() { if (!$CAMIC.viewer.canvasDrawInstance) return; const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + spen.close(); + //close spen if ( canvasDraw._draws_data_.length && confirm(`Do You Want To Save Annotation Label Before You Leave?`) @@ -2561,62 +2570,52 @@ async function rootCallback({root, parent, items}) { } /* Enhance Tool */ -function enhance(data) { - document.querySelector('[title="Enhance"]').previousSibling.checked = true; - if (!setEnhance) { - // $UI.message.add('infoOn Movement, enhance would be undone', 2000); - $CAMIC.viewer.addHandler('zoom', unenhance); - $CAMIC.viewer.addHandler('pan', unenhance); - setEnhance = true; - } - - // Canvas information - const canvas = $CAMIC.viewer.canvas.firstChild; - const context = canvas.getContext('2d'); - let width = canvas.width; let height = canvas.height; - var img = context.getImageData(0, 0, width, height); - - if (data.status == 'Histogram Eq') { - context.putImageData(clahe(img, 64, 0.015), 0, 0); - } else if (data.status == 'Edge') { - context.putImageData(edgedetect(img, 150, 0, 0, width, height), 0, 0); - } else if (data.status == 'Sharpen') { - var filter = [[-1, -1, -1], [-1, 14, -1], [-1, -1, -1]]; - filter = [filter, filter, filter]; - var newimg = new ImageData(new Uint8ClampedArray(applyfilter(img, filter, 0, 0, width, height)), width, height); - context.putImageData(newimg, 0, 0); - } else if (data.status == 'Custom') { - var input = prompt('Enter the 2D Kernel: Eg : [[1, 0], [0, 1]]'); var f=0; - // JSON Parse test - try { - var filter = JSON.parse(input); var sz = filter[0].length; - } catch (e) { - console.error(e); - alert('Invalid Kernel : ' + input); - return; - } - // 2D array check - for (var r=0; rinfoOn Movement, enhance would be undone', 2000); + $CAMIC.viewer.addHandler('zoom', unenhance); + $CAMIC.viewer.addHandler('pan', unenhance); + set_enhance = true;} + + // Canvas information + const canvas = $CAMIC.viewer.canvas.firstChild; + const context = canvas.getContext('2d'); + let width = canvas.width, height = canvas.height; + var img = context.getImageData(0,0,width,height); + var data = img.data; + + if (e.status == 'Histogram Eq') + context.putImageData(clahe(img,64,0.015),0,0); + else if (e.status == 'Edge'){ + context.putImageData(edgedetect_sobel(img,80),0,0);} + else if(e.status == 'Sharpen'){ + var filter = [[-1,-1,-1],[-1,14,-1],[-1,-1,-1]]; + context.putImageData(applyfilter(img,filter),0,0);} + + else if (e.status == "Custom"){ + var input = prompt("Enter the 2D Kernel: Eg : [[1, 0], [0, 1]]"), f=0; + // JSON Parse test + try{ + var filter = JSON.parse(input), sz = filter[0].length;} + catch{ + alert("Invalid Kernel : " + input); + return;} + // 2D array check + for(var r=0; r - + + + @@ -179,6 +181,10 @@ + + + + - diff --git a/common/enhance.js b/common/enhance.js new file mode 100644 index 000000000..b851f4b07 --- /dev/null +++ b/common/enhance.js @@ -0,0 +1,437 @@ +/** +CLAHE, Edge detect Sobel, Edge detect Canny, Apply filter 1, Grayscaler +Misc: conv 1, recrop, grayscale, mapcolor, copycolor +(changes input) +**/ + +/** Histogram equalization CLAHE + *@param {ImageData} image + *@param {int} tiles + *@param {float} clip + *@return {ImageData} image +**/ +function clahe(imgData, tiles=32, clip=0.1) { + var tilesize = [tiles, tiles]; + var clipLimit = clip; + var min=255, max=0; + var intens=[]; + var levels=[]; + var j=0; + for (var i=0;i50) { + imgData.data[indexToFill-3]= Math.floor(imgData.data[indexToFill-3]*chn); + imgData.data[indexToFill-2] = Math.floor(imgData.data[indexToFill-2]*chn); + imgData.data[indexToFill-1] = Math.floor(imgData.data[indexToFill-1]*chn); + // imgData.data[indexToFill] = 255 - result; + } + } + return imgData; +} + + +/** Apply Filter 1 +*@param {ImageData} image +*@param {2D array} kernel +*@param {int} Ouput_channels (0/4) +*@return {ImageData} image +**/ +// color cannot be displaced +function applyfilter(imgData,kernel,ch=4){ + var height = imgData.height,width=imgData.width, data=imgData.data; + var graydata = grayscale(data,width,height); + var newdata = conv_1(graydata,width,height,kernel); + if(ch==4) mapcolor(newdata,data,width,height); + else imgData = {data:newdata,width:width,height:height}; + return imgData; +} + + +/** Grayscaler + *@param {ImageData} image + *@param {int} Ouput_channels (0/4) + *@return {ImageData} image +**/ +function grayscaler(imgData,ch=4){ + var height = imgData.height,width=imgData.width, data=imgData.data; + var cl = grayscale(data,width,height); + if(ch==4) copycolor(cl,data,width,height); + else imgData = {data:cl,width:width,height:height}; + return imgData; +} + + +/** Sobel Edge detect + *@param {ImageData} image + *@param {float} threshold + *@param {int} Ouput_channels (0/4) + *@return {ImageData} image +**/ +function edgedetect_sobel(imgData,threshold,ch=4){ + //console.time('SOBEL'); + var kernelx = + [[-1, 0, 1], + [-2, 0, 2], + [-1, 0, 1]]; + var kernely = + [[-1, -2, -1], + [0, 0, 0], + [1, 2, 1]]; + var height = imgData.height,width=imgData.width, data=imgData.data; + + var gray = grayscale(data,width,height); + //console.log("gray"); + var gaussMatrix = [[2,3,2],[3,5,3],[2,3,2]]; + gray = conv_1(gray,width,height,gaussMatrix) + //console.log("blur"); + + var imgx = conv_1(gray,width,height,kernelx); + //console.log("imgx"); + var imgy = conv_1(gray,width,height,kernely); + //console.log("imgy"); + for(var i=0;ithreshold? 255-sum:255-0; + setpix1(gray,j,i,width,sum); + } + } + if (ch==4) copycolor(gray,imgData.data,width,height); + else imgData = {data:gray,width:width,height:height}; + //console.timeEnd('SOBEL'); + return imgData; +} + + +/** Canny Edge detect + *@param {ImageData} image + *@param {int} lower_threshold + *@param {int} Upper_threshold + *@param {int} Gaussian_Kernal_Size + *@param {int} Kernel_Sigma + *@param {int} Ouput_channels (0/4) + *@return {ImageData} image +**/ +function edgedetect_canny(imgData,lt=20,ut=60,kerneln=5,sigma=1.8,ch=4){ + var height = imgData.height,width=imgData.width, data=imgData.data; + //console.time('Canny'); + var gradientMagnitude = new Array(width*height); + const gradientDirection = new Array(width*height); + var kernelh = Math.floor(kerneln/2), gaussMatrix=[]; + + for(var i=-kernelh;i= -Math.PI / 8 && pom < Math.PI / 8) || (pom <= -7 * Math.PI / 8 && pom > 7 * Math.PI / 8)) + gradientDirection[index] = 0; //0 + else if ((pom >= Math.PI / 8 && pom < 3 * Math.PI / 8) || (pom <= -5 * Math.PI / 8 && pom > -7 * Math.PI / 8)) + gradientDirection[index] = 1; //45 + else if ((pom >= 3 * Math.PI / 8 && pom <= 5 * Math.PI / 8) || (-3 * Math.PI / 8 >= pom && pom > -5 * Math.PI / 8)) + gradientDirection[index] = 2; //90 + else if ((pom < -Math.PI / 8 && pom >= -3 * Math.PI / 8) || (pom > 5 * Math.PI / 8 && pom <= 7 * Math.PI / 8)) + gradientDirection[index] = 3; //135 / -45 + else gradientDirection[index] = 0; + } + } + // console.log("non max suppression"); + var copy = new Array(width*height), degrees = {0 : [{x:1, y:2}, {x:1, y:0}], 1 : [{x: 0, y: 2}, {x: 2, y: 0}], 2 : [{x: 0, y: 1}, {x: 2, y: 1}], 3 : [{x: 0, y: 0}, {x: 2, y: 2}]}; + for (let y = 1; y < height-1; y++) { + for (let x = 1; x < width-1; x++) { + index = y * width + x; + var pixNeighbors = degrees[gradientDirection[index]]; + //pixel neighbors to compare + var pix1 = getpix1(gradientMagnitude,y-1+pixNeighbors[0].y,x-1+pixNeighbors[0].x,width); + var pix2 = getpix1(gradientMagnitude,y-1+pixNeighbors[1].y,x-1+pixNeighbors[1].x,width); + if (pix1 > gradientMagnitude[index] || + pix2 > gradientMagnitude[index] || + (pix2 === gradientMagnitude[index] && + pix1 < gradientMagnitude[index])) + setpix1(copy,y,x,width, 0); + else + setpix1(copy,y,x,width, getpix1(gradientMagnitude,y,x,width)); + } + } + // console.log("thresholding") + let gradientMagnitudeLt = copy.map(value => value>lt ? value:0); + // console.log("hysterisis"); + var _traverseEdge = function(current, imgData,width,height, threshold, traversed) { + var group = [current]; + var neighbors = getEdgeNeighbors(current, imgData,width,height, threshold, traversed); + for(var i = 0; i < neighbors.length; i++){ + group = group.concat(_traverseEdge(neighbors[i], imgData, threshold, traversed.concat(group)));} + return group;}; + var getEdgeNeighbors = function(inn, imgData,width,height, threshold, includedEdges) { + var neighbors = [] + var x = inn%width, y= (inn-x)/width,r=1; + for(var i=x-r;ithreshold && (includedEdges === undefined || includedEdges.indexOf(index) === -1)) + neighbors.push(index);}} + return neighbors;}; + var realEdges = [] + for (var y = 1; y < height - 1; y++) + for (var x = 1; x < width - 1; x++) { + index = y * width + x; + if (gradientMagnitudeLt[index] > ut && realEdges[index] === undefined) {//accept as a definite edge + var group = _traverseEdge(index, gradientMagnitudeLt,width,height, ut, []); + for(var i = 0; i < group.length; i++) + realEdges[group[i]] = true; + } + } + for (var y = 1; y < height - 1; y++) + for (var x = 1; x < width - 1; x++) { + index = y * width + x; + if (realEdges[index] === undefined) + setpix1(gradientMagnitudeLt,y,x,width, 0); + else + setpix1(gradientMagnitudeLt,y,x,width, 255); + } + //console.timeEnd('Canny'); + if (ch==4) copycolor(gradientMagnitudeLt,imgData.data,width,height); + else imgData = {data:gradientMagnitudeLt,width:width,height:height}; + return imgData; +} + +/*******************************************************************************************************************************************************************/ +/*******************************************************************************************************************************************************************/ +/*******************************************************************************************************************************************************************/ +/** Apply Conv 1 +*@param {1ch Array} image +*@param {int} width +*@param {int} height +*@param {2D array} kernel +*@return {1ch Array} Data +**/ +// Corners are ignored +function conv_1(data,width,height, kernel){ + var kernelSizey = kernel.length; + var kernelSizex = kernel[0].length; + var kernelSizey2 = Math.floor(kernelSizey/2) + var kernelSizex2 = Math.floor(kernelSizex/2) + var newdata = new Array(width*height); + + var sumk=0; + for(var x = 0; x < kernelSizex; x++) + for(var y = 0; y < kernelSizey; y++) + sumk+=kernel[y][x]; + if(!sumk)sumk=1; + + var x1=0,x2=width-kernelSizex; + var y1=0,y2=height-kernelSizey; + for(var i=x1;i1 +function grayscale(data,width,height){ + var x1=0,x2=width; + var y1=0,y2=height; + var newdata = new Array(width*height); + for(var i=x1;i 4 with color +function mapcolor(bw,color,width,height){ + var x1=0,x2=width; + var y1=0,y2=height; + for(var i=x1;i 4 without color +function copycolor(bw,color,width,height){ + var x1=0,x2=width; + var y1=0,y2=height; + for(var i=x1;i255)a=255; + return a; +} +function buildcdf(hist, num_bins, num_pixels, clipLimit){ + var excess = 0; + for(var i=0;iclipLimit){ + excess = excess + hist[i] - clipLimit; + hist[i] = clipLimit; + } + } + var addExcess = excess/num_bins; + var cumuhist = []; + cumuhist[0] = hist[0] + addExcess; + for(var i=1;i=w && y=h && x=h && x>=w) { + idx = (h * w) - 1; + } else { + idx = (y * w + x); + } + var val = Math.floor(intens[idx]); + hist[val]++; + } + } + return hist; +} diff --git a/common/smartpen/autoalign.css b/common/smartpen/autoalign.css new file mode 100644 index 000000000..1bb07ef6e --- /dev/null +++ b/common/smartpen/autoalign.css @@ -0,0 +1,56 @@ + /*upper blocks*/ +#align_menu *{ + font-size:24px; + color:#0000be;/**/ + padding: 0; + display: inline-block; + margin: 3px 3px 3px 3px; +} +#align_menu button{ + height:30px; + width:40px; + transition-duration: .2s; + border: 2px solid red;/**/ + border-radius: 5px; +} +#align_mode{ + color:red; +} +#align_flag1:checked ~ #align_openbtn{ + width:40px !important; +} +#align_flag1:checked ~ #align_mode{ + transform: translateX(-40px) translateY(30px); + transition-duration: .2s; +} +#align_flag1:checked ~ #align_undoalign{ + transform: translateX(0px) translateY(30px); + transition-duration: .2s; +} +#align_flag1:checked ~ #align_setting{ + transform: translateX(40px) translateY(30px); + transition-duration: .2s; +} +/*Add more options here*/ + +/*sliders*/ +#align_sliders{ + display: none; + background-color:#ffffff4d;/**/ + transform: translateX(40px) translateY(60px); +} +#align_sliders *{ + color:#07c691;/**/ + font-size: 12px; +} +#align_sliders input{ + background:#90adfe;/**/ + border: solid 1px #3f73ff;/**/ + border-radius: 8px; + height: 7px; + outline: none; + -webkit-appearance: none; +} +#align_flag2:checked ~ #align_sliders{ + display: block; +} diff --git a/common/smartpen/autoalign.js b/common/smartpen/autoalign.js new file mode 100644 index 000000000..61e13558b --- /dev/null +++ b/common/smartpen/autoalign.js @@ -0,0 +1,251 @@ +//requires enhance and tippy module + +/* class for align */ +class smartpen{ + constructor(){ + this.init(); + } + init(){ + this.canvas; + this.context; + this.threshold = this.t = 90; + this.smoothness= 4*4; + this.radius = 30; + this.mode = 0; + // 0 for Off, 1 for after finish, 2 for realtime + this.menubar; + this.menuon = false; + this.undo; + } + initcanvas(canvas){ + this.canvas = canvas; + this.context = canvas.getContext('2d'); + } + // call edge detection + detect(x1,y1,x,y,ch=4){ + var newdata = this.context.getImageData(x1,y1,x,y); + var dst = edgedetect_canny(newdata,~~(this.t/1.5),this.t,5,1.8,ch); + return dst; + } + // collects edges + edge(x1,y1,x,y){ + var dist = []; + var temp = this.detect(x1,y1,x,y,1); + var l = temp.data; + for (var i = x1;i200) + dist.push({x:i,y:j}) + } + return dist; + } + // get optimum pts for a stroke + apply(arr){ + var n = 3, th = this.smoothness, lambda=1.5; + var pop = [],clean=[], mod1=[], mod2=[], final=[], nearest = [], pts = [], f = arr.length-1,f1=0,f2=0, prev; + + //populate + for(var i=0;i<=f-1;i++){ + var d = this.distance(arr[i],arr[i+1]); + var m = spen.min([~~(Math.sqrt(d)/4), 150]); + if(d>=64)pop.push(...this.populate([arr[i],arr[i+1]],m)); + else pop.push(arr[i]);} + pop.push(arr[f]); + f = pop.length-1; + // clean + clean.push(pop[0]); + for(var i=1;i<=f;i++) + if(!this.eqlpt(pop[i],pop[i-1])) + clean.push(pop[i]); + f = clean.length-1; + // Nearest + for(var i = 0;i<=f;i++){ + var c = this.nearest(clean[i],true); + nearest.push(c[0]); + pts.push(c[1]);} + // Continuity Heuristic 1 + for(var i = 0; i<=f ;i++){ + var mean = this.average(nearest,i,n); + var dist = pts[i], mind = 10e10,point = clean[i],near=clean[i]; + for(var j =0;jth){ + mod2.push(mean);f1++;} + else {mod2.push(mod1[i]);f2++;}} + //console.log("heuristic 2: ",f2/f1); + // copy + console.log(f); + for(var i = 0; i<=f ;i++) + final.push(mod2[i]); + return final; + } + // get optimum pt for a pt + nearest(point,all = false){ + var r = this.radius + var x1 = this.max([point.x-Math.floor(r/2),0]) + var y1 = this.max([point.y-Math.floor(r/2),0]) + var x = this.min([r,this.canvas.width-x1]) + var y = this.min([r,this.canvas.height-y1]) + + var dist = [], trials=3; this.t=this.threshold; + while(!dist.length && --trials){ + dist = this.edge(x1,y1,x,y) + this.t/=2;this.t=~~this.t; + } + var near = point; + var d , mind = 1000000*100000; + for(var i =0;i=mod1.length||j==0||(i+j)<0)continue; + mean.x+=mod1[(i+j)].x + mean.y+=mod1[(i+j)].y + c+=1;} + mean.x=~~(mean.x/c);mean.y=~~(mean.y/c); + return mean; + } + // equal points + eqlpt(a,b){ + if(a.x==b.x && a.y==b.y)return true; + else return false; + } + max(a){ + var m = -100000000000; + for(var i=0;ia[i]? m : a[i]; + } + return m; + } + min(a){ + var m = 100000000000; + for(var i=0;i