made with requirebin
Last active
May 23, 2017 06:37
-
-
Save jcblw/23492b20e96d58529cd54bd20955b978 to your computer and use it in GitHub Desktop.
requirebin sketch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const Delaunator = require('delaunator') | |
| function colorDistance(r1,g1,b1,r2,g2,b2){ | |
| const dr = r1 - r2 | |
| const dg = g1 - g2 | |
| const db = b1 - b2 | |
| return Math.sqrt( dr*dr + dg*dg + db*db ) | |
| } | |
| function sumDistance(imageData,x,y,n){ | |
| const pixels = imageData.data | |
| const imageSizeX = imageData.width | |
| const imageSizeY = imageData.height | |
| // position information | |
| let px, py, i, j, pos | |
| // distance accumulator | |
| let dSum = 0 | |
| // colors | |
| const r1 = pixels[4*(imageSizeX*y+x) + 0] | |
| const g1 = pixels[4*(imageSizeX*y+x) + 1] | |
| const b1 = pixels[4*(imageSizeX*y+x) + 2] | |
| let r2, g2, b2 | |
| for( i=-n; i<=n; i+=1 ){ | |
| for( j=-n; j<=n; j+=1 ){ | |
| // Tile the image if we are at an end | |
| px = (x+i) % imageSizeX | |
| px = (px>0)?px:-px | |
| py = (y+j) % imageSizeY | |
| py = (py>0)?py:-py | |
| // Get the colors of this pixel | |
| pos = 4*(imageSizeX*py + px) | |
| r2 = pixels[pos+0] | |
| g2 = pixels[pos+1] | |
| b2 = pixels[pos+2] | |
| // Work with the pixel | |
| dSum += colorDistance(r1,g1,b1,r2,g2,b2) | |
| } | |
| } | |
| return dSum | |
| } | |
| function centerOfN (x1, x2, x3) { | |
| return (x1 + x2 + x3) / 3 | |
| } | |
| function centerOfTriangle(x1, y1, x2, y2, x3, y3) { | |
| return [centerOfN(x1, x2, x3), centerOfN(y1, y2, y3)] | |
| } | |
| function computeDistanceData(imageData,n){ | |
| const pixels = imageData.data | |
| const imageSizeX = imageData.width | |
| const imageSizeY = imageData.height | |
| let x,y | |
| const data = [] | |
| for( x=0; x<imageSizeX; x+=1 ){ | |
| for( y=0; y<imageSizeY; y+=1 ){ | |
| data.push( { | |
| x: x, | |
| y: y, | |
| d: sumDistance(imageData,x,y,n) | |
| } ) | |
| } | |
| } | |
| return data | |
| } | |
| function byDecreasingD(a,b){ | |
| return b.d - a.d | |
| } | |
| function findCorners(canvas,apertureSize,numPoints){ | |
| // Get the raw pixel data from the canvas | |
| const context = canvas.getContext('2d') | |
| const imageData = context.getImageData(0,0,canvas.width,canvas.height) | |
| // Compute and return the results | |
| const results = computeDistanceData(imageData,apertureSize) | |
| return results.sort(byDecreasingD).slice(0,numPoints) | |
| } | |
| function circleFrom(center, radius, angle = 2 * Math.PI) { | |
| const circle = new window.Path2D() | |
| circle.moveTo( | |
| ...center.map((x, y) => { | |
| return ([x + radius, y + radius]) | |
| }) | |
| ); | |
| circle.arc(...center, radius, 0, angle) | |
| return circle | |
| } | |
| // TODO: reuse canvas? | |
| const el = document.createElement('canvas') | |
| const context = el.getContext('2d') | |
| const input = document.getElementById('file-input') | |
| input.addEventListener('change', function() { | |
| const file = this.files[0] | |
| loadImage(URL.createObjectURL(file), onImageLoad) | |
| }, false) | |
| function onImageLoad(err, img) { | |
| if (err) { | |
| // remember when this was ok | |
| alert(err.message) | |
| return | |
| } | |
| el.width = img.width | |
| el.height = img.height | |
| context.drawImage(img, 0, 0) | |
| const corners = findCorners(el, 1, 10000) | |
| const coords = corners.map(({x, y}) => [x, y]) | |
| const triangles = new Delaunator(coords).triangles | |
| const build = [] | |
| for (let i = 0; i < triangles.length; i += 3) { | |
| let x0 = coords[triangles[i]][0] | |
| let y0 = coords[triangles[i]][1] | |
| let x1 = coords[triangles[i + 1]][0] | |
| let y1 = coords[triangles[i + 1]][1] | |
| let x2 = coords[triangles[i + 2]][0] | |
| let y2 = coords[triangles[i + 2]][1] | |
| let center = centerOfTriangle(x0, y0, x1, y1, x2, y2) | |
| let pixel = context.getImageData(center[0], center[1], 1, 1) | |
| let data = pixel.data; | |
| let rgb = `rgb(${data[0]}, ${data[1]}, ${data[2]})` | |
| build.push(function() { | |
| context.beginPath() | |
| context.moveTo(x0, y0) | |
| context.lineTo(x1, y1) | |
| context.lineTo(x2, y2) | |
| context.fillStyle = rgb | |
| context.fill() | |
| }) | |
| } | |
| context.clearRect(0, 0, el.width, el.height) | |
| build.forEach(fn => fn()) | |
| const url = el.toDataURL('image/png') | |
| const newImg = new Image() | |
| newImg.src = url | |
| newImg.style = 'max-width:100%' | |
| document.body.appendChild(newImg) | |
| } | |
| function loadImage(src, callback) { | |
| const img = new Image() | |
| img.src = src | |
| img.onload = callback.bind(null, null, img) | |
| img.onerror = callback | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| setTimeout(function(){ | |
| ;require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"delaunator":[function(require,module,exports){ | |
| 'use strict'; | |
| module.exports = Delaunator; | |
| function Delaunator(points, getX, getY) { | |
| if (!getX) getX = defaultGetX; | |
| if (!getY) getY = defaultGetY; | |
| var minX = Infinity; | |
| var minY = Infinity; | |
| var maxX = -Infinity; | |
| var maxY = -Infinity; | |
| var coords = this.coords = []; | |
| var ids = this.ids = new Uint32Array(points.length); | |
| for (var i = 0; i < points.length; i++) { | |
| var p = points[i]; | |
| var x = getX(p); | |
| var y = getY(p); | |
| ids[i] = i; | |
| coords[2 * i] = x; | |
| coords[2 * i + 1] = y; | |
| if (x < minX) minX = x; | |
| if (y < minY) minY = y; | |
| if (x > maxX) maxX = x; | |
| if (y > maxY) maxY = y; | |
| } | |
| var cx = (minX + maxX) / 2; | |
| var cy = (minY + maxY) / 2; | |
| var minDist = Infinity; | |
| var i0, i1, i2; | |
| // pick a seed point close to the centroid | |
| for (i = 0; i < points.length; i++) { | |
| var d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]); | |
| if (d < minDist) { | |
| i0 = i; | |
| minDist = d; | |
| } | |
| } | |
| minDist = Infinity; | |
| // find the point closest to the seed | |
| for (i = 0; i < points.length; i++) { | |
| if (i === i0) continue; | |
| d = dist(coords[2 * i0], coords[2 * i0 + 1], coords[2 * i], coords[2 * i + 1]); | |
| if (d < minDist && d > 0) { | |
| i1 = i; | |
| minDist = d; | |
| } | |
| } | |
| var minRadius = Infinity; | |
| // find the third point which forms the smallest circumcircle with the first two | |
| for (i = 0; i < points.length; i++) { | |
| if (i === i0 || i === i1) continue; | |
| var r = circumradius( | |
| coords[2 * i0], coords[2 * i0 + 1], | |
| coords[2 * i1], coords[2 * i1 + 1], | |
| coords[2 * i], coords[2 * i + 1]); | |
| if (r < minRadius) { | |
| i2 = i; | |
| minRadius = r; | |
| } | |
| } | |
| if (minRadius === Infinity) { | |
| throw new Error('No Delaunay triangulation exists for this input.'); | |
| } | |
| // swap the order of the seed points for counter-clockwise orientation | |
| if (area(coords[2 * i0], coords[2 * i0 + 1], | |
| coords[2 * i1], coords[2 * i1 + 1], | |
| coords[2 * i2], coords[2 * i2 + 1]) < 0) { | |
| var tmp = i1; | |
| i1 = i2; | |
| i2 = tmp; | |
| } | |
| var i0x = coords[2 * i0]; | |
| var i0y = coords[2 * i0 + 1]; | |
| var i1x = coords[2 * i1]; | |
| var i1y = coords[2 * i1 + 1]; | |
| var i2x = coords[2 * i2]; | |
| var i2y = coords[2 * i2 + 1]; | |
| var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y); | |
| this._cx = center.x; | |
| this._cy = center.y; | |
| // sort the points by distance from the seed triangle circumcenter | |
| quicksort(ids, coords, 0, ids.length - 1, center.x, center.y); | |
| // initialize a hash table for storing edges of the advancing convex hull | |
| this._hashSize = Math.ceil(Math.sqrt(points.length)); | |
| this._hash = []; | |
| for (i = 0; i < this._hashSize; i++) this._hash[i] = null; | |
| // initialize a circular doubly-linked list that will hold an advancing convex hull | |
| var e = this.hull = insertNode(coords, i0); | |
| this._hashEdge(e); | |
| e.t = 0; | |
| e = insertNode(coords, i1, e); | |
| this._hashEdge(e); | |
| e.t = 1; | |
| e = insertNode(coords, i2, e); | |
| this._hashEdge(e); | |
| e.t = 2; | |
| var maxTriangles = 2 * points.length - 5; | |
| var triangles = this.triangles = new Uint32Array(maxTriangles * 3); | |
| triangles[0] = i0; | |
| triangles[1] = i1; | |
| triangles[2] = i2; | |
| this.trianglesLen = 3; | |
| var adjacent = this.adjacent = new Int32Array(maxTriangles * 3); | |
| adjacent[0] = -1; | |
| adjacent[1] = -1; | |
| adjacent[2] = -1; | |
| var xp, yp; | |
| for (var k = 0; k < ids.length; k++) { | |
| i = ids[k]; | |
| x = coords[2 * i]; | |
| y = coords[2 * i + 1]; | |
| // skip duplicate points | |
| if (x === xp && y === yp) continue; | |
| xp = x; | |
| yp = y; | |
| // skip seed triangle points | |
| if ((x === i0x && y === i0y) || | |
| (x === i1x && y === i1y) || | |
| (x === i2x && y === i2y)) continue; | |
| // find a visible edge on the convex hull using edge hash | |
| var startKey = this._hashKey(x, y); | |
| var key = startKey; | |
| var start; | |
| do { | |
| start = this._hash[key]; | |
| key = (key + 1) % this._hashSize; | |
| } while ((!start || start.removed) && key !== startKey); | |
| e = start; | |
| while (area(x, y, e.x, e.y, e.next.x, e.next.y) >= 0) { | |
| e = e.next; | |
| if (e === start) { | |
| throw new Error('Something is wrong with the input points.'); | |
| } | |
| } | |
| var walkBack = e === start; | |
| // add the first triangle from the point | |
| var t = this._addTriangle(i, e); | |
| adjacent[t] = -1; | |
| adjacent[t + 1] = -1; | |
| this._link(t + 2, e.t); | |
| e.t = t; // keep track of boundary triangles on the hull | |
| e = insertNode(coords, i, e); | |
| // recursively flip triangles from the point until they satisfy the Delaunay condition | |
| e.t = this._legalize(t + 2); | |
| // walk forward through the hull, adding more triangles and flipping recursively | |
| var q = e.next; | |
| while (area(x, y, q.x, q.y, q.next.x, q.next.y) < 0) { | |
| t = this._addTriangle(i, q); | |
| this._link(t, q.prev.t); | |
| adjacent[t + 1] = -1; | |
| this._link(t + 2, q.t); | |
| q.prev.t = this._legalize(t + 2); | |
| this.hull = removeNode(q); | |
| q = q.next; | |
| } | |
| if (walkBack) { | |
| // walk backward from the other side, adding more triangles and flipping | |
| q = e.prev; | |
| while (area(x, y, q.prev.x, q.prev.y, q.x, q.y) < 0) { | |
| t = this._addTriangle(i, q.prev); | |
| adjacent[t] = -1; | |
| this._link(t + 1, q.t); | |
| this._link(t + 2, q.prev.t); | |
| this._legalize(t + 2); | |
| q.prev.t = t; | |
| this.hull = removeNode(q); | |
| q = q.prev; | |
| } | |
| } | |
| // save the two new edges in the hash table | |
| this._hashEdge(e); | |
| this._hashEdge(e.prev); | |
| } | |
| // trim typed triangle mesh arrays | |
| this.triangles = triangles.subarray(0, this.trianglesLen); | |
| this.adjacent = adjacent.subarray(0, this.trianglesLen); | |
| } | |
| Delaunator.prototype = { | |
| _hashEdge: function (e) { | |
| this._hash[this._hashKey(e.x, e.y)] = e; | |
| }, | |
| _hashKey: function (x, y) { | |
| var dx = x - this._cx; | |
| var dy = y - this._cy; | |
| // use pseudo-angle: a measure that monotonically increases | |
| // with real angle, but doesn't require expensive trigonometry | |
| var p = 1 - dx / (Math.abs(dx) + Math.abs(dy)); | |
| return Math.floor((2 + (dy < 0 ? -p : p)) * (this._hashSize / 4)); | |
| }, | |
| _legalize: function (a) { | |
| var triangles = this.triangles; | |
| var coords = this.coords; | |
| var adjacent = this.adjacent; | |
| var b = adjacent[a]; | |
| var a0 = a - a % 3; | |
| var b0 = b - b % 3; | |
| var al = a0 + (a + 1) % 3; | |
| var ar = a0 + (a + 2) % 3; | |
| var br = b0 + (b + 1) % 3; | |
| var bl = b0 + (b + 2) % 3; | |
| var p0 = triangles[ar]; | |
| var pr = triangles[a]; | |
| var pl = triangles[al]; | |
| var p1 = triangles[bl]; | |
| var illegal = inCircle( | |
| coords[2 * p0], coords[2 * p0 + 1], | |
| coords[2 * pr], coords[2 * pr + 1], | |
| coords[2 * pl], coords[2 * pl + 1], | |
| coords[2 * p1], coords[2 * p1 + 1]); | |
| if (illegal) { | |
| triangles[a] = p1; | |
| triangles[b] = p0; | |
| this._link(a, adjacent[bl]); | |
| this._link(b, adjacent[ar]); | |
| this._link(ar, bl); | |
| this._legalize(a); | |
| return this._legalize(br); | |
| } | |
| return ar; | |
| }, | |
| _link: function (a, b) { | |
| this.adjacent[a] = b; | |
| if (b !== -1) this.adjacent[b] = a; | |
| }, | |
| _addTriangle(i, e) { | |
| var t = this.trianglesLen; | |
| this.triangles[t] = e.i; | |
| this.triangles[t + 1] = i; | |
| this.triangles[t + 2] = e.next.i; | |
| this.trianglesLen += 3; | |
| return t; | |
| } | |
| }; | |
| function dist(ax, ay, bx, by) { | |
| var dx = ax - bx; | |
| var dy = ay - by; | |
| return dx * dx + dy * dy; | |
| } | |
| function area(px, py, qx, qy, rx, ry) { | |
| return (qy - py) * (rx - qx) - (qx - px) * (ry - qy); | |
| } | |
| function inCircle(ax, ay, bx, by, cx, cy, px, py) { | |
| ax -= px; | |
| ay -= py; | |
| bx -= px; | |
| by -= py; | |
| cx -= px; | |
| cy -= py; | |
| var ap = ax * ax + ay * ay; | |
| var bp = bx * bx + by * by; | |
| var cp = cx * cx + cy * cy; | |
| var det = ax * (by * cp - bp * cy) - | |
| ay * (bx * cp - bp * cx) + | |
| ap * (bx * cy - by * cx); | |
| return det < 0; | |
| } | |
| function circumradius(ax, ay, bx, by, cx, cy) { | |
| bx -= ax; | |
| by -= ay; | |
| cx -= ax; | |
| cy -= ay; | |
| var bl = bx * bx + by * by; | |
| var cl = cx * cx + cy * cy; | |
| if (bl === 0 || cl === 0) return Infinity; | |
| var d = bx * cy - by * cx; | |
| if (d === 0) return Infinity; | |
| var x = (cy * bl - by * cl) * 0.5 / d; | |
| var y = (bx * cl - cx * bl) * 0.5 / d; | |
| return x * x + y * y; | |
| } | |
| function circumcenter(ax, ay, bx, by, cx, cy) { | |
| bx -= ax; | |
| by -= ay; | |
| cx -= ax; | |
| cy -= ay; | |
| var bl = bx * bx + by * by; | |
| var cl = cx * cx + cy * cy; | |
| var d = bx * cy - by * cx; | |
| var x = (cy * bl - by * cl) * 0.5 / d; | |
| var y = (bx * cl - cx * bl) * 0.5 / d; | |
| return { | |
| x: ax + x, | |
| y: ay + y | |
| }; | |
| } | |
| // create a new node in a doubly linked list | |
| function insertNode(coords, i, prev) { | |
| var node = { | |
| i: i, | |
| x: coords[2 * i], | |
| y: coords[2 * i + 1], | |
| t: 0, | |
| prev: null, | |
| next: null, | |
| removed: false | |
| }; | |
| if (!prev) { | |
| node.prev = node; | |
| node.next = node; | |
| } else { | |
| node.next = prev.next; | |
| node.prev = prev; | |
| prev.next.prev = node; | |
| prev.next = node; | |
| } | |
| return node; | |
| } | |
| function removeNode(node) { | |
| node.prev.next = node.next; | |
| node.next.prev = node.prev; | |
| node.removed = true; | |
| return node.prev; | |
| } | |
| function quicksort(ids, coords, left, right, cx, cy) { | |
| var i, j, temp; | |
| if (right - left <= 20) { | |
| for (i = left + 1; i <= right; i++) { | |
| temp = ids[i]; | |
| j = i - 1; | |
| while (j >= left && compare(coords, ids[j], temp, cx, cy) > 0) ids[j + 1] = ids[j--]; | |
| ids[j + 1] = temp; | |
| } | |
| } else { | |
| var median = (left + right) >> 1; | |
| i = left + 1; | |
| j = right; | |
| swap(ids, median, i); | |
| if (compare(coords, ids[left], ids[right], cx, cy) > 0) swap(ids, left, right); | |
| if (compare(coords, ids[i], ids[right], cx, cy) > 0) swap(ids, i, right); | |
| if (compare(coords, ids[left], ids[i], cx, cy) > 0) swap(ids, left, i); | |
| temp = ids[i]; | |
| while (true) { | |
| do i++; while (compare(coords, ids[i], temp, cx, cy) < 0); | |
| do j--; while (compare(coords, ids[j], temp, cx, cy) > 0); | |
| if (j < i) break; | |
| swap(ids, i, j); | |
| } | |
| ids[left + 1] = ids[j]; | |
| ids[j] = temp; | |
| if (right - i + 1 >= j - left) { | |
| quicksort(ids, coords, i, right, cx, cy); | |
| quicksort(ids, coords, left, j - 1, cx, cy); | |
| } else { | |
| quicksort(ids, coords, left, j - 1, cx, cy); | |
| quicksort(ids, coords, i, right, cx, cy); | |
| } | |
| } | |
| } | |
| function compare(coords, i, j, cx, cy) { | |
| var d1 = dist(coords[2 * i], coords[2 * i + 1], cx, cy); | |
| var d2 = dist(coords[2 * j], coords[2 * j + 1], cx, cy); | |
| return (d1 - d2) || (coords[2 * i] - coords[2 * j]) || (coords[2 * i + 1] - coords[2 * j + 1]); | |
| } | |
| function swap(arr, i, j) { | |
| var tmp = arr[i]; | |
| arr[i] = arr[j]; | |
| arr[j] = tmp; | |
| } | |
| function defaultGetX(p) { | |
| return p[0]; | |
| } | |
| function defaultGetY(p) { | |
| return p[1]; | |
| } | |
| },{}]},{},[]) | |
| //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2hvbWUvYWRtaW4vYnJvd3NlcmlmeS1jZG4vbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsImRlbGF1bmF0b3IiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IERlbGF1bmF0b3I7XG5cbmZ1bmN0aW9uIERlbGF1bmF0b3IocG9pbnRzLCBnZXRYLCBnZXRZKSB7XG5cbiAgICBpZiAoIWdldFgpIGdldFggPSBkZWZhdWx0R2V0WDtcbiAgICBpZiAoIWdldFkpIGdldFkgPSBkZWZhdWx0R2V0WTtcblxuICAgIHZhciBtaW5YID0gSW5maW5pdHk7XG4gICAgdmFyIG1pblkgPSBJbmZpbml0eTtcbiAgICB2YXIgbWF4WCA9IC1JbmZpbml0eTtcbiAgICB2YXIgbWF4WSA9IC1JbmZpbml0eTtcblxuICAgIHZhciBjb29yZHMgPSB0aGlzLmNvb3JkcyA9IFtdO1xuICAgIHZhciBpZHMgPSB0aGlzLmlkcyA9IG5ldyBVaW50MzJBcnJheShwb2ludHMubGVuZ3RoKTtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcG9pbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBwID0gcG9pbnRzW2ldO1xuICAgICAgICB2YXIgeCA9IGdldFgocCk7XG4gICAgICAgIHZhciB5ID0gZ2V0WShwKTtcbiAgICAgICAgaWRzW2ldID0gaTtcbiAgICAgICAgY29vcmRzWzIgKiBpXSA9IHg7XG4gICAgICAgIGNvb3Jkc1syICogaSArIDFdID0geTtcbiAgICAgICAgaWYgKHggPCBtaW5YKSBtaW5YID0geDtcbiAgICAgICAgaWYgKHkgPCBtaW5ZKSBtaW5ZID0geTtcbiAgICAgICAgaWYgKHggPiBtYXhYKSBtYXhYID0geDtcbiAgICAgICAgaWYgKHkgPiBtYXhZKSBtYXhZID0geTtcbiAgICB9XG5cbiAgICB2YXIgY3ggPSAobWluWCArIG1heFgpIC8gMjtcbiAgICB2YXIgY3kgPSAobWluWSArIG1heFkpIC8gMjtcblxuICAgIHZhciBtaW5EaXN0ID0gSW5maW5pdHk7XG4gICAgdmFyIGkwLCBpMSwgaTI7XG5cbiAgICAvLyBwaWNrIGEgc2VlZCBwb2ludCBjbG9zZSB0byB0aGUgY2VudHJvaWRcbiAgICBmb3IgKGkgPSAwOyBpIDwgcG9pbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBkID0gZGlzdChjeCwgY3ksIGNvb3Jkc1syICogaV0sIGNvb3Jkc1syICogaSArIDFdKTtcbiAgICAgICAgaWYgKGQgPCBtaW5EaXN0KSB7XG4gICAgICAgICAgICBpMCA9IGk7XG4gICAgICAgICAgICBtaW5EaXN0ID0gZDtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIG1pbkRpc3QgPSBJbmZpbml0eTtcblxuICAgIC8vIGZpbmQgdGhlIHBvaW50IGNsb3Nlc3QgdG8gdGhlIHNlZWRcbiAgICBmb3IgKGkgPSAwOyBpIDwgcG9pbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmIChpID09PSBpMCkgY29udGludWU7XG4gICAgICAgIGQgPSBkaXN0KGNvb3Jkc1syICogaTBdLCBjb29yZHNbMiAqIGkwICsgMV0sIGNvb3Jkc1syICogaV0sIGNvb3Jkc1syICogaSArIDFdKTtcbiAgICAgICAgaWYgKGQgPCBtaW5EaXN0ICYmIGQgPiAwKSB7XG4gICAgICAgICAgICBpMSA9IGk7XG4gICAgICAgICAgICBtaW5EaXN0ID0gZDtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHZhciBtaW5SYWRpdXMgPSBJbmZpbml0eTtcblxuICAgIC8vIGZpbmQgdGhlIHRoaXJkIHBvaW50IHdoaWNoIGZvcm1zIHRoZSBzbWFsbGVzdCBjaXJjdW1jaXJjbGUgd2l0aCB0aGUgZmlyc3QgdHdvXG4gICAgZm9yIChpID0gMDsgaSA8IHBvaW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgICBpZiAoaSA9PT0gaTAgfHwgaSA9PT0gaTEpIGNvbnRpbnVlO1xuXG4gICAgICAgIHZhciByID0gY2lyY3VtcmFkaXVzKFxuICAgICAgICAgICAgY29vcmRzWzIgKiBpMF0sIGNvb3Jkc1syICogaTAgKyAxXSxcbiAgICAgICAgICAgIGNvb3Jkc1syICogaTFdLCBjb29yZHNbMiAqIGkxICsgMV0sXG4gICAgICAgICAgICBjb29yZHNbMiAqIGldLCBjb29yZHNbMiAqIGkgKyAxXSk7XG5cbiAgICAgICAgaWYgKHIgPCBtaW5SYWRpdXMpIHtcbiAgICAgICAgICAgIGkyID0gaTtcbiAgICAgICAgICAgIG1pblJhZGl1cyA9IHI7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAobWluUmFkaXVzID09PSBJbmZpbml0eSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ05vIERlbGF1bmF5IHRyaWFuZ3VsYXRpb24gZXhpc3RzIGZvciB0aGlzIGlucHV0LicpO1xuICAgIH1cblxuICAgIC8vIHN3YXAgdGhlIG9yZGVyIG9mIHRoZSBzZWVkIHBvaW50cyBmb3IgY291bnRlci1jbG9ja3dpc2Ugb3JpZW50YXRpb25cbiAgICBpZiAoYXJlYShjb29yZHNbMiAqIGkwXSwgY29vcmRzWzIgKiBpMCArIDFdLFxuICAgICAgICAgICAgIGNvb3Jkc1syICogaTFdLCBjb29yZHNbMiAqIGkxICsgMV0sXG4gICAgICAgICAgICAgY29vcmRzWzIgKiBpMl0sIGNvb3Jkc1syICogaTIgKyAxXSkgPCAwKSB7XG5cbiAgICAgICAgdmFyIHRtcCA9IGkxO1xuICAgICAgICBpMSA9IGkyO1xuICAgICAgICBpMiA9IHRtcDtcbiAgICB9XG5cbiAgICB2YXIgaTB4ID0gY29vcmRzWzIgKiBpMF07XG4gICAgdmFyIGkweSA9IGNvb3Jkc1syICogaTAgKyAxXTtcbiAgICB2YXIgaTF4ID0gY29vcmRzWzIgKiBpMV07XG4gICAgdmFyIGkxeSA9IGNvb3Jkc1syICogaTEgKyAxXTtcbiAgICB2YXIgaTJ4ID0gY29vcmRzWzIgKiBpMl07XG4gICAgdmFyIGkyeSA9IGNvb3Jkc1syICogaTIgKyAxXTtcblxuICAgIHZhciBjZW50ZXIgPSBjaXJjdW1jZW50ZXIoaTB4LCBpMHksIGkxeCwgaTF5LCBpMngsIGkyeSk7XG4gICAgdGhpcy5fY3ggPSBjZW50ZXIueDtcbiAgICB0aGlzLl9jeSA9IGNlbnRlci55O1xuXG4gICAgLy8gc29ydCB0aGUgcG9pbnRzIGJ5IGRpc3RhbmNlIGZyb20gdGhlIHNlZWQgdHJpYW5nbGUgY2lyY3VtY2VudGVyXG4gICAgcXVpY2tzb3J0KGlkcywgY29vcmRzLCAwLCBpZHMubGVuZ3RoIC0gMSwgY2VudGVyLngsIGNlbnRlci55KTtcblxuICAgIC8vIGluaXRpYWxpemUgYSBoYXNoIHRhYmxlIGZvciBzdG9yaW5nIGVkZ2VzIG9mIHRoZSBhZHZhbmNpbmcgY29udmV4IGh1bGxcbiAgICB0aGlzLl9oYXNoU2l6ZSA9IE1hdGguY2VpbChNYXRoLnNxcnQocG9pbnRzLmxlbmd0aCkpO1xuICAgIHRoaXMuX2hhc2ggPSBbXTtcbiAgICBmb3IgKGkgPSAwOyBpIDwgdGhpcy5faGFzaFNpemU7IGkrKykgdGhpcy5faGFzaFtpXSA9IG51bGw7XG5cbiAgICAvLyBpbml0aWFsaXplIGEgY2lyY3VsYXIgZG91Ymx5LWxpbmtlZCBsaXN0IHRoYXQgd2lsbCBob2xkIGFuIGFkdmFuY2luZyBjb252ZXggaHVsbFxuICAgIHZhciBlID0gdGhpcy5odWxsID0gaW5zZXJ0Tm9kZShjb29yZHMsIGkwKTtcbiAgICB0aGlzLl9oYXNoRWRnZShlKTtcbiAgICBlLnQgPSAwO1xuICAgIGUgPSBpbnNlcnROb2RlKGNvb3JkcywgaTEsIGUpO1xuICAgIHRoaXMuX2hhc2hFZGdlKGUpO1xuICAgIGUudCA9IDE7XG4gICAgZSA9IGluc2VydE5vZGUoY29vcmRzLCBpMiwgZSk7XG4gICAgdGhpcy5faGFzaEVkZ2UoZSk7XG4gICAgZS50ID0gMjtcblxuICAgIHZhciBtYXhUcmlhbmdsZXMgPSAyICogcG9pbnRzLmxlbmd0aCAtIDU7XG4gICAgdmFyIHRyaWFuZ2xlcyA9IHRoaXMudHJpYW5nbGVzID0gbmV3IFVpbnQzMkFycmF5KG1heFRyaWFuZ2xlcyAqIDMpO1xuICAgIHRyaWFuZ2xlc1swXSA9IGkwO1xuICAgIHRyaWFuZ2xlc1sxXSA9IGkxO1xuICAgIHRyaWFuZ2xlc1syXSA9IGkyO1xuICAgIHRoaXMudHJpYW5nbGVzTGVuID0gMztcblxuICAgIHZhciBhZGphY2VudCA9IHRoaXMuYWRqYWNlbnQgPSBuZXcgSW50MzJBcnJheShtYXhUcmlhbmdsZXMgKiAzKTtcbiAgICBhZGphY2VudFswXSA9IC0xO1xuICAgIGFkamFjZW50WzFdID0gLTE7XG4gICAgYWRqYWNlbnRbMl0gPSAtMTtcblxuICAgIHZhciB4cCwgeXA7XG4gICAgZm9yICh2YXIgayA9IDA7IGsgPCBpZHMubGVuZ3RoOyBrKyspIHtcbiAgICAgICAgaSA9IGlkc1trXTtcbiAgICAgICAgeCA9IGNvb3Jkc1syICogaV07XG4gICAgICAgIHkgPSBjb29yZHNbMiAqIGkgKyAxXTtcblxuICAgICAgICAvLyBza2lwIGR1cGxpY2F0ZSBwb2ludHNcbiAgICAgICAgaWYgKHggPT09IHhwICYmIHkgPT09IHlwKSBjb250aW51ZTtcbiAgICAgICAgeHAgPSB4O1xuICAgICAgICB5cCA9IHk7XG5cbiAgICAgICAgLy8gc2tpcCBzZWVkIHRyaWFuZ2xlIHBvaW50c1xuICAgICAgICBpZiAoKHggPT09IGkweCAmJiB5ID09PSBpMHkpIHx8XG4gICAgICAgICAgICAoeCA9PT0gaTF4ICYmIHkgPT09IGkxeSkgfHxcbiAgICAgICAgICAgICh4ID09PSBpMnggJiYgeSA9PT0gaTJ5KSkgY29udGludWU7XG5cbiAgICAgICAgLy8gZmluZCBhIHZpc2libGUgZWRnZSBvbiB0aGUgY29udmV4IGh1bGwgdXNpbmcgZWRnZSBoYXNoXG4gICAgICAgIHZhciBzdGFydEtleSA9IHRoaXMuX2hhc2hLZXkoeCwgeSk7XG4gICAgICAgIHZhciBrZXkgPSBzdGFydEtleTtcbiAgICAgICAgdmFyIHN0YXJ0O1xuICAgICAgICBkbyB7XG4gICAgICAgICAgICBzdGFydCA9IHRoaXMuX2hhc2hba2V5XTtcbiAgICAgICAgICAgIGtleSA9IChrZXkgKyAxKSAlIHRoaXMuX2hhc2hTaXplO1xuICAgICAgICB9IHdoaWxlICgoIXN0YXJ0IHx8IHN0YXJ0LnJlbW92ZWQpICYmIGtleSAhPT0gc3RhcnRLZXkpO1xuXG4gICAgICAgIGUgPSBzdGFydDtcbiAgICAgICAgd2hpbGUgKGFyZWEoeCwgeSwgZS54LCBlLnksIGUubmV4dC54LCBlLm5leHQueSkgPj0gMCkge1xuICAgICAgICAgICAgZSA9IGUubmV4dDtcbiAgICAgICAgICAgIGlmIChlID09PSBzdGFydCkge1xuICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignU29tZXRoaW5nIGlzIHdyb25nIHdpdGggdGhlIGlucHV0IHBvaW50cy4nKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciB3YWxrQmFjayA9IGUgPT09IHN0YXJ0O1xuXG4gICAgICAgIC8vIGFkZCB0aGUgZmlyc3QgdHJpYW5nbGUgZnJvbSB0aGUgcG9pbnRcbiAgICAgICAgdmFyIHQgPSB0aGlzLl9hZGRUcmlhbmdsZShpLCBlKTtcbiAgICAgICAgYWRqYWNlbnRbdF0gPSAtMTtcbiAgICAgICAgYWRqYWNlbnRbdCArIDFdID0gLTE7XG4gICAgICAgIHRoaXMuX2xpbmsodCArIDIsIGUudCk7XG5cbiAgICAgICAgZS50ID0gdDsgLy8ga2VlcCB0cmFjayBvZiBib3VuZGFyeSB0cmlhbmdsZXMgb24gdGhlIGh1bGxcbiAgICAgICAgZSA9IGluc2VydE5vZGUoY29vcmRzLCBpLCBlKTtcblxuICAgICAgICAvLyByZWN1cnNpdmVseSBmbGlwIHRyaWFuZ2xlcyBmcm9tIHRoZSBwb2ludCB1bnRpbCB0aGV5IHNhdGlzZnkgdGhlIERlbGF1bmF5IGNvbmRpdGlvblxuICAgICAgICBlLnQgPSB0aGlzLl9sZWdhbGl6ZSh0ICsgMik7XG5cbiAgICAgICAgLy8gd2FsayBmb3J3YXJkIHRocm91Z2ggdGhlIGh1bGwsIGFkZGluZyBtb3JlIHRyaWFuZ2xlcyBhbmQgZmxpcHBpbmcgcmVjdXJzaXZlbHlcbiAgICAgICAgdmFyIHEgPSBlLm5leHQ7XG4gICAgICAgIHdoaWxlIChhcmVhKHgsIHksIHEueCwgcS55LCBxLm5leHQueCwgcS5uZXh0LnkpIDwgMCkge1xuXG4gICAgICAgICAgICB0ID0gdGhpcy5fYWRkVHJpYW5nbGUoaSwgcSk7XG4gICAgICAgICAgICB0aGlzLl9saW5rKHQsIHEucHJldi50KTtcbiAgICAgICAgICAgIGFkamFjZW50W3QgKyAxXSA9IC0xO1xuICAgICAgICAgICAgdGhpcy5fbGluayh0ICsgMiwgcS50KTtcblxuICAgICAgICAgICAgcS5wcmV2LnQgPSB0aGlzLl9sZWdhbGl6ZSh0ICsgMik7XG5cbiAgICAgICAgICAgIHRoaXMuaHVsbCA9IHJlbW92ZU5vZGUocSk7XG4gICAgICAgICAgICBxID0gcS5uZXh0O1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHdhbGtCYWNrKSB7XG4gICAgICAgICAgICAvLyB3YWxrIGJhY2t3YXJkIGZyb20gdGhlIG90aGVyIHNpZGUsIGFkZGluZyBtb3JlIHRyaWFuZ2xlcyBhbmQgZmxpcHBpbmdcbiAgICAgICAgICAgIHEgPSBlLnByZXY7XG4gICAgICAgICAgICB3aGlsZSAoYXJlYSh4LCB5LCBxLnByZXYueCwgcS5wcmV2LnksIHEueCwgcS55KSA8IDApIHtcblxuICAgICAgICAgICAgICAgIHQgPSB0aGlzLl9hZGRUcmlhbmdsZShpLCBxLnByZXYpO1xuICAgICAgICAgICAgICAgIGFkamFjZW50W3RdID0gLTE7XG4gICAgICAgICAgICAgICAgdGhpcy5fbGluayh0ICsgMSwgcS50KTtcbiAgICAgICAgICAgICAgICB0aGlzLl9saW5rKHQgKyAyLCBxLnByZXYudCk7XG5cbiAgICAgICAgICAgICAgICB0aGlzLl9sZWdhbGl6ZSh0ICsgMik7XG5cbiAgICAgICAgICAgICAgICBxLnByZXYudCA9IHQ7XG4gICAgICAgICAgICAgICAgdGhpcy5odWxsID0gcmVtb3ZlTm9kZShxKTtcbiAgICAgICAgICAgICAgICBxID0gcS5wcmV2O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gc2F2ZSB0aGUgdHdvIG5ldyBlZGdlcyBpbiB0aGUgaGFzaCB0YWJsZVxuICAgICAgICB0aGlzLl9oYXNoRWRnZShlKTtcbiAgICAgICAgdGhpcy5faGFzaEVkZ2UoZS5wcmV2KTtcbiAgICB9XG5cbiAgICAvLyB0cmltIHR5cGVkIHRyaWFuZ2xlIG1lc2ggYXJyYXlzXG4gICAgdGhpcy50cmlhbmdsZXMgPSB0cmlhbmdsZXMuc3ViYXJyYXkoMCwgdGhpcy50cmlhbmdsZXNMZW4pO1xuICAgIHRoaXMuYWRqYWNlbnQgPSBhZGphY2VudC5zdWJhcnJheSgwLCB0aGlzLnRyaWFuZ2xlc0xlbik7XG59XG5cbkRlbGF1bmF0b3IucHJvdG90eXBlID0ge1xuXG4gICAgX2hhc2hFZGdlOiBmdW5jdGlvbiAoZSkge1xuICAgICAgICB0aGlzLl9oYXNoW3RoaXMuX2hhc2hLZXkoZS54LCBlLnkpXSA9IGU7XG4gICAgfSxcblxuICAgIF9oYXNoS2V5OiBmdW5jdGlvbiAoeCwgeSkge1xuICAgICAgICB2YXIgZHggPSB4IC0gdGhpcy5fY3g7XG4gICAgICAgIHZhciBkeSA9IHkgLSB0aGlzLl9jeTtcbiAgICAgICAgLy8gdXNlIHBzZXVkby1hbmdsZTogYSBtZWFzdXJlIHRoYXQgbW9ub3RvbmljYWxseSBpbmNyZWFzZXNcbiAgICAgICAgLy8gd2l0aCByZWFsIGFuZ2xlLCBidXQgZG9lc24ndCByZXF1aXJlIGV4cGVuc2l2ZSB0cmlnb25vbWV0cnlcbiAgICAgICAgdmFyIHAgPSAxIC0gZHggLyAoTWF0aC5hYnMoZHgpICsgTWF0aC5hYnMoZHkpKTtcbiAgICAgICAgcmV0dXJuIE1hdGguZmxvb3IoKDIgKyAoZHkgPCAwID8gLXAgOiBwKSkgKiAodGhpcy5faGFzaFNpemUgLyA0KSk7XG4gICAgfSxcblxuICAgIF9sZWdhbGl6ZTogZnVuY3Rpb24gKGEpIHtcbiAgICAgICAgdmFyIHRyaWFuZ2xlcyA9IHRoaXMudHJpYW5nbGVzO1xuICAgICAgICB2YXIgY29vcmRzID0gdGhpcy5jb29yZHM7XG4gICAgICAgIHZhciBhZGphY2VudCA9IHRoaXMuYWRqYWNlbnQ7XG5cbiAgICAgICAgdmFyIGIgPSBhZGphY2VudFthXTtcblxuICAgICAgICB2YXIgYTAgPSBhIC0gYSAlIDM7XG4gICAgICAgIHZhciBiMCA9IGIgLSBiICUgMztcblxuICAgICAgICB2YXIgYWwgPSBhMCArIChhICsgMSkgJSAzO1xuICAgICAgICB2YXIgYXIgPSBhMCArIChhICsgMikgJSAzO1xuICAgICAgICB2YXIgYnIgPSBiMCArIChiICsgMSkgJSAzO1xuICAgICAgICB2YXIgYmwgPSBiMCArIChiICsgMikgJSAzO1xuXG4gICAgICAgIHZhciBwMCA9IHRyaWFuZ2xlc1thcl07XG4gICAgICAgIHZhciBwciA9IHRyaWFuZ2xlc1thXTtcbiAgICAgICAgdmFyIHBsID0gdHJpYW5nbGVzW2FsXTtcbiAgICAgICAgdmFyIHAxID0gdHJpYW5nbGVzW2JsXTtcblxuICAgICAgICB2YXIgaWxsZWdhbCA9IGluQ2lyY2xlKFxuICAgICAgICAgICAgY29vcmRzWzIgKiBwMF0sIGNvb3Jkc1syICogcDAgKyAxXSxcbiAgICAgICAgICAgIGNvb3Jkc1syICogcHJdLCBjb29yZHNbMiAqIHByICsgMV0sXG4gICAgICAgICAgICBjb29yZHNbMiAqIHBsXSwgY29vcmRzWzIgKiBwbCArIDFdLFxuICAgICAgICAgICAgY29vcmRzWzIgKiBwMV0sIGNvb3Jkc1syICogcDEgKyAxXSk7XG5cbiAgICAgICAgaWYgKGlsbGVnYWwpIHtcbiAgICAgICAgICAgIHRyaWFuZ2xlc1thXSA9IHAxO1xuICAgICAgICAgICAgdHJpYW5nbGVzW2JdID0gcDA7XG5cbiAgICAgICAgICAgIHRoaXMuX2xpbmsoYSwgYWRqYWNlbnRbYmxdKTtcbiAgICAgICAgICAgIHRoaXMuX2xpbmsoYiwgYWRqYWNlbnRbYXJdKTtcbiAgICAgICAgICAgIHRoaXMuX2xpbmsoYXIsIGJsKTtcblxuICAgICAgICAgICAgdGhpcy5fbGVnYWxpemUoYSk7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5fbGVnYWxpemUoYnIpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGFyO1xuICAgIH0sXG5cbiAgICBfbGluazogZnVuY3Rpb24gKGEsIGIpIHtcbiAgICAgICAgdGhpcy5hZGphY2VudFthXSA9IGI7XG4gICAgICAgIGlmIChiICE9PSAtMSkgdGhpcy5hZGphY2VudFtiXSA9IGE7XG4gICAgfSxcblxuICAgIF9hZGRUcmlhbmdsZShpLCBlKSB7XG4gICAgICAgIHZhciB0ID0gdGhpcy50cmlhbmdsZXNMZW47XG4gICAgICAgIHRoaXMudHJpYW5nbGVzW3RdID0gZS5pO1xuICAgICAgICB0aGlzLnRyaWFuZ2xlc1t0ICsgMV0gPSBpO1xuICAgICAgICB0aGlzLnRyaWFuZ2xlc1t0ICsgMl0gPSBlLm5leHQuaTtcbiAgICAgICAgdGhpcy50cmlhbmdsZXNMZW4gKz0gMztcbiAgICAgICAgcmV0dXJuIHQ7XG4gICAgfVxufTtcblxuZnVuY3Rpb24gZGlzdChheCwgYXksIGJ4LCBieSkge1xuICAgIHZhciBkeCA9IGF4IC0gYng7XG4gICAgdmFyIGR5ID0gYXkgLSBieTtcbiAgICByZXR1cm4gZHggKiBkeCArIGR5ICogZHk7XG59XG5cbmZ1bmN0aW9uIGFyZWEocHgsIHB5LCBxeCwgcXksIHJ4LCByeSkge1xuICAgIHJldHVybiAocXkgLSBweSkgKiAocnggLSBxeCkgLSAocXggLSBweCkgKiAocnkgLSBxeSk7XG59XG5cbmZ1bmN0aW9uIGluQ2lyY2xlKGF4LCBheSwgYngsIGJ5LCBjeCwgY3ksIHB4LCBweSkge1xuICAgIGF4IC09IHB4O1xuICAgIGF5IC09IHB5O1xuICAgIGJ4IC09IHB4O1xuICAgIGJ5IC09IHB5O1xuICAgIGN4IC09IHB4O1xuICAgIGN5IC09IHB5O1xuXG4gICAgdmFyIGFwID0gYXggKiBheCArIGF5ICogYXk7XG4gICAgdmFyIGJwID0gYnggKiBieCArIGJ5ICogYnk7XG4gICAgdmFyIGNwID0gY3ggKiBjeCArIGN5ICogY3k7XG5cbiAgICB2YXIgZGV0ID0gYXggKiAoYnkgKiBjcCAtIGJwICogY3kpIC1cbiAgICAgICAgICAgICAgYXkgKiAoYnggKiBjcCAtIGJwICogY3gpICtcbiAgICAgICAgICAgICAgYXAgKiAoYnggKiBjeSAtIGJ5ICogY3gpO1xuXG4gICAgcmV0dXJuIGRldCA8IDA7XG59XG5cbmZ1bmN0aW9uIGNpcmN1bXJhZGl1cyhheCwgYXksIGJ4LCBieSwgY3gsIGN5KSB7XG4gICAgYnggLT0gYXg7XG4gICAgYnkgLT0gYXk7XG4gICAgY3ggLT0gYXg7XG4gICAgY3kgLT0gYXk7XG5cbiAgICB2YXIgYmwgPSBieCAqIGJ4ICsgYnkgKiBieTtcbiAgICB2YXIgY2wgPSBjeCAqIGN4ICsgY3kgKiBjeTtcblxuICAgIGlmIChibCA9PT0gMCB8fCBjbCA9PT0gMCkgcmV0dXJuIEluZmluaXR5O1xuXG4gICAgdmFyIGQgPSBieCAqIGN5IC0gYnkgKiBjeDtcbiAgICBpZiAoZCA9PT0gMCkgcmV0dXJuIEluZmluaXR5O1xuXG4gICAgdmFyIHggPSAoY3kgKiBibCAtIGJ5ICogY2wpICogMC41IC8gZDtcbiAgICB2YXIgeSA9IChieCAqIGNsIC0gY3ggKiBibCkgKiAwLjUgLyBkO1xuXG4gICAgcmV0dXJuIHggKiB4ICsgeSAqIHk7XG59XG5cbmZ1bmN0aW9uIGNpcmN1bWNlbnRlcihheCwgYXksIGJ4LCBieSwgY3gsIGN5KSB7XG4gICAgYnggLT0gYXg7XG4gICAgYnkgLT0gYXk7XG4gICAgY3ggLT0gYXg7XG4gICAgY3kgLT0gYXk7XG5cbiAgICB2YXIgYmwgPSBieCAqIGJ4ICsgYnkgKiBieTtcbiAgICB2YXIgY2wgPSBjeCAqIGN4ICsgY3kgKiBjeTtcblxuICAgIHZhciBkID0gYnggKiBjeSAtIGJ5ICogY3g7XG5cbiAgICB2YXIgeCA9IChjeSAqIGJsIC0gYnkgKiBjbCkgKiAwLjUgLyBkO1xuICAgIHZhciB5ID0gKGJ4ICogY2wgLSBjeCAqIGJsKSAqIDAuNSAvIGQ7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICB4OiBheCArIHgsXG4gICAgICAgIHk6IGF5ICsgeVxuICAgIH07XG59XG5cbi8vIGNyZWF0ZSBhIG5ldyBub2RlIGluIGEgZG91Ymx5IGxpbmtlZCBsaXN0XG5mdW5jdGlvbiBpbnNlcnROb2RlKGNvb3JkcywgaSwgcHJldikge1xuICAgIHZhciBub2RlID0ge1xuICAgICAgICBpOiBpLFxuICAgICAgICB4OiBjb29yZHNbMiAqIGldLFxuICAgICAgICB5OiBjb29yZHNbMiAqIGkgKyAxXSxcbiAgICAgICAgdDogMCxcbiAgICAgICAgcHJldjogbnVsbCxcbiAgICAgICAgbmV4dDogbnVsbCxcbiAgICAgICAgcmVtb3ZlZDogZmFsc2VcbiAgICB9O1xuXG4gICAgaWYgKCFwcmV2KSB7XG4gICAgICAgIG5vZGUucHJldiA9IG5vZGU7XG4gICAgICAgIG5vZGUubmV4dCA9IG5vZGU7XG5cbiAgICB9IGVsc2Uge1xuICAgICAgICBub2RlLm5leHQgPSBwcmV2Lm5leHQ7XG4gICAgICAgIG5vZGUucHJldiA9IHByZXY7XG4gICAgICAgIHByZXYubmV4dC5wcmV2ID0gbm9kZTtcbiAgICAgICAgcHJldi5uZXh0ID0gbm9kZTtcbiAgICB9XG4gICAgcmV0dXJuIG5vZGU7XG59XG5cbmZ1bmN0aW9uIHJlbW92ZU5vZGUobm9kZSkge1xuICAgIG5vZGUucHJldi5uZXh0ID0gbm9kZS5uZXh0O1xuICAgIG5vZGUubmV4dC5wcmV2ID0gbm9kZS5wcmV2O1xuICAgIG5vZGUucmVtb3ZlZCA9IHRydWU7XG4gICAgcmV0dXJuIG5vZGUucHJldjtcbn1cblxuZnVuY3Rpb24gcXVpY2tzb3J0KGlkcywgY29vcmRzLCBsZWZ0LCByaWdodCwgY3gsIGN5KSB7XG4gICAgdmFyIGksIGosIHRlbXA7XG5cbiAgICBpZiAocmlnaHQgLSBsZWZ0IDw9IDIwKSB7XG4gICAgICAgIGZvciAoaSA9IGxlZnQgKyAxOyBpIDw9IHJpZ2h0OyBpKyspIHtcbiAgICAgICAgICAgIHRlbXAgPSBpZHNbaV07XG4gICAgICAgICAgICBqID0gaSAtIDE7XG4gICAgICAgICAgICB3aGlsZSAoaiA+PSBsZWZ0ICYmIGNvbXBhcmUoY29vcmRzLCBpZHNbal0sIHRlbXAsIGN4LCBjeSkgPiAwKSBpZHNbaiArIDFdID0gaWRzW2otLV07XG4gICAgICAgICAgICBpZHNbaiArIDFdID0gdGVtcDtcbiAgICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAgIHZhciBtZWRpYW4gPSAobGVmdCArIHJpZ2h0KSA+PiAxO1xuICAgICAgICBpID0gbGVmdCArIDE7XG4gICAgICAgIGogPSByaWdodDtcbiAgICAgICAgc3dhcChpZHMsIG1lZGlhbiwgaSk7XG4gICAgICAgIGlmIChjb21wYXJlKGNvb3JkcywgaWRzW2xlZnRdLCBpZHNbcmlnaHRdLCBjeCwgY3kpID4gMCkgc3dhcChpZHMsIGxlZnQsIHJpZ2h0KTtcbiAgICAgICAgaWYgKGNvbXBhcmUoY29vcmRzLCBpZHNbaV0sIGlkc1tyaWdodF0sIGN4LCBjeSkgPiAwKSBzd2FwKGlkcywgaSwgcmlnaHQpO1xuICAgICAgICBpZiAoY29tcGFyZShjb29yZHMsIGlkc1tsZWZ0XSwgaWRzW2ldLCBjeCwgY3kpID4gMCkgc3dhcChpZHMsIGxlZnQsIGkpO1xuXG4gICAgICAgIHRlbXAgPSBpZHNbaV07XG4gICAgICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICAgICAgICBkbyBpKys7IHdoaWxlIChjb21wYXJlKGNvb3JkcywgaWRzW2ldLCB0ZW1wLCBjeCwgY3kpIDwgMCk7XG4gICAgICAgICAgICBkbyBqLS07IHdoaWxlIChjb21wYXJlKGNvb3JkcywgaWRzW2pdLCB0ZW1wLCBjeCwgY3kpID4gMCk7XG4gICAgICAgICAgICBpZiAoaiA8IGkpIGJyZWFrO1xuICAgICAgICAgICAgc3dhcChpZHMsIGksIGopO1xuICAgICAgICB9XG4gICAgICAgIGlkc1tsZWZ0ICsgMV0gPSBpZHNbal07XG4gICAgICAgIGlkc1tqXSA9IHRlbXA7XG5cbiAgICAgICAgaWYgKHJpZ2h0IC0gaSArIDEgPj0gaiAtIGxlZnQpIHtcbiAgICAgICAgICAgIHF1aWNrc29ydChpZHMsIGNvb3JkcywgaSwgcmlnaHQsIGN4LCBjeSk7XG4gICAgICAgICAgICBxdWlja3NvcnQoaWRzLCBjb29yZHMsIGxlZnQsIGogLSAxLCBjeCwgY3kpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcXVpY2tzb3J0KGlkcywgY29vcmRzLCBsZWZ0LCBqIC0gMSwgY3gsIGN5KTtcbiAgICAgICAgICAgIHF1aWNrc29ydChpZHMsIGNvb3JkcywgaSwgcmlnaHQsIGN4LCBjeSk7XG4gICAgICAgIH1cbiAgICB9XG59XG5cbmZ1bmN0aW9uIGNvbXBhcmUoY29vcmRzLCBpLCBqLCBjeCwgY3kpIHtcbiAgICB2YXIgZDEgPSBkaXN0KGNvb3Jkc1syICogaV0sIGNvb3Jkc1syICogaSArIDFdLCBjeCwgY3kpO1xuICAgIHZhciBkMiA9IGRpc3QoY29vcmRzWzIgKiBqXSwgY29vcmRzWzIgKiBqICsgMV0sIGN4LCBjeSk7XG4gICAgcmV0dXJuIChkMSAtIGQyKSB8fCAoY29vcmRzWzIgKiBpXSAtIGNvb3Jkc1syICogal0pIHx8IChjb29yZHNbMiAqIGkgKyAxXSAtIGNvb3Jkc1syICogaiArIDFdKTtcbn1cblxuZnVuY3Rpb24gc3dhcChhcnIsIGksIGopIHtcbiAgICB2YXIgdG1wID0gYXJyW2ldO1xuICAgIGFycltpXSA9IGFycltqXTtcbiAgICBhcnJbal0gPSB0bXA7XG59XG5cbmZ1bmN0aW9uIGRlZmF1bHRHZXRYKHApIHtcbiAgICByZXR1cm4gcFswXTtcbn1cbmZ1bmN0aW9uIGRlZmF1bHRHZXRZKHApIHtcbiAgICByZXR1cm4gcFsxXTtcbn1cbiJdfQ== | |
| const Delaunator = require('delaunator') | |
| function colorDistance(r1,g1,b1,r2,g2,b2){ | |
| const dr = r1 - r2 | |
| const dg = g1 - g2 | |
| const db = b1 - b2 | |
| return Math.sqrt( dr*dr + dg*dg + db*db ) | |
| } | |
| function sumDistance(imageData,x,y,n){ | |
| const pixels = imageData.data | |
| const imageSizeX = imageData.width | |
| const imageSizeY = imageData.height | |
| // position information | |
| let px, py, i, j, pos | |
| // distance accumulator | |
| let dSum = 0 | |
| // colors | |
| const r1 = pixels[4*(imageSizeX*y+x) + 0] | |
| const g1 = pixels[4*(imageSizeX*y+x) + 1] | |
| const b1 = pixels[4*(imageSizeX*y+x) + 2] | |
| let r2, g2, b2 | |
| for( i=-n; i<=n; i+=1 ){ | |
| for( j=-n; j<=n; j+=1 ){ | |
| // Tile the image if we are at an end | |
| px = (x+i) % imageSizeX | |
| px = (px>0)?px:-px | |
| py = (y+j) % imageSizeY | |
| py = (py>0)?py:-py | |
| // Get the colors of this pixel | |
| pos = 4*(imageSizeX*py + px) | |
| r2 = pixels[pos+0] | |
| g2 = pixels[pos+1] | |
| b2 = pixels[pos+2] | |
| // Work with the pixel | |
| dSum += colorDistance(r1,g1,b1,r2,g2,b2) | |
| } | |
| } | |
| return dSum | |
| } | |
| function centerOfN (x1, x2, x3) { | |
| return (x1 + x2 + x3) / 3 | |
| } | |
| function centerOfTriangle(x1, y1, x2, y2, x3, y3) { | |
| return [centerOfN(x1, x2, x3), centerOfN(y1, y2, y3)] | |
| } | |
| function computeDistanceData(imageData,n){ | |
| const pixels = imageData.data | |
| const imageSizeX = imageData.width | |
| const imageSizeY = imageData.height | |
| let x,y | |
| const data = [] | |
| for( x=0; x<imageSizeX; x+=1 ){ | |
| for( y=0; y<imageSizeY; y+=1 ){ | |
| data.push( { | |
| x: x, | |
| y: y, | |
| d: sumDistance(imageData,x,y,n) | |
| } ) | |
| } | |
| } | |
| return data | |
| } | |
| function byDecreasingD(a,b){ | |
| return b.d - a.d | |
| } | |
| function findCorners(canvas,apertureSize,numPoints){ | |
| // Get the raw pixel data from the canvas | |
| const context = canvas.getContext('2d') | |
| const imageData = context.getImageData(0,0,canvas.width,canvas.height) | |
| // Compute and return the results | |
| const results = computeDistanceData(imageData,apertureSize) | |
| return results.sort(byDecreasingD).slice(0,numPoints) | |
| } | |
| function circleFrom(center, radius, angle = 2 * Math.PI) { | |
| const circle = new window.Path2D() | |
| circle.moveTo( | |
| ...center.map((x, y) => { | |
| return ([x + radius, y + radius]) | |
| }) | |
| ); | |
| circle.arc(...center, radius, 0, angle) | |
| return circle | |
| } | |
| // TODO: reuse canvas? | |
| const el = document.createElement('canvas') | |
| const context = el.getContext('2d') | |
| const input = document.getElementById('file-input') | |
| input.addEventListener('change', function() { | |
| const file = this.files[0] | |
| loadImage(URL.createObjectURL(file), onImageLoad) | |
| }, false) | |
| function onImageLoad(err, img) { | |
| if (err) { | |
| // remember when this was ok | |
| alert(err.message) | |
| return | |
| } | |
| el.width = img.width | |
| el.height = img.height | |
| context.drawImage(img, 0, 0) | |
| const corners = findCorners(el, 1, 10000) | |
| const coords = corners.map(({x, y}) => [x, y]) | |
| const triangles = new Delaunator(coords).triangles | |
| const build = [] | |
| for (let i = 0; i < triangles.length; i += 3) { | |
| let x0 = coords[triangles[i]][0] | |
| let y0 = coords[triangles[i]][1] | |
| let x1 = coords[triangles[i + 1]][0] | |
| let y1 = coords[triangles[i + 1]][1] | |
| let x2 = coords[triangles[i + 2]][0] | |
| let y2 = coords[triangles[i + 2]][1] | |
| let center = centerOfTriangle(x0, y0, x1, y1, x2, y2) | |
| let pixel = context.getImageData(center[0], center[1], 1, 1) | |
| let data = pixel.data; | |
| let rgb = `rgb(${data[0]}, ${data[1]}, ${data[2]})` | |
| build.push(function() { | |
| context.beginPath() | |
| context.moveTo(x0, y0) | |
| context.lineTo(x1, y1) | |
| context.lineTo(x2, y2) | |
| context.fillStyle = rgb | |
| context.fill() | |
| }) | |
| } | |
| context.clearRect(0, 0, el.width, el.height) | |
| build.forEach(fn => fn()) | |
| const url = el.toDataURL('image/png') | |
| const newImg = new Image() | |
| newImg.src = url | |
| newImg.style = 'max-width:100%' | |
| document.body.appendChild(newImg) | |
| } | |
| function loadImage(src, callback) { | |
| const img = new Image() | |
| img.src = src | |
| img.onload = callback.bind(null, null, img) | |
| img.onerror = callback | |
| } | |
| ;}, 0) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "requirebin-sketch", | |
| "version": "1.0.0", | |
| "dependencies": { | |
| "delaunator": "1.0.2" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- contents of this file will be placed inside the <body> --> | |
| <input type='file' id='file-input' /> | |
| <div id='content'></div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- contents of this file will be placed inside the <head> --> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment