├── .gitignore ├── .prettierrc.json ├── LICENSE.txt ├── README.md ├── demo ├── canvasFilters.js ├── demo.css ├── demo.js ├── index.html ├── measureBlur.js └── worker.js ├── package-lock.json ├── package.json └── src ├── cli.js └── measureBlur.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Timo Taglieber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inspector Bokeh 2 | 3 | Experimental JavaScript library to measure blur in images. Try out the [live demo](https://timotaglieber.de/inspector-bokeh) or read this [blog post](https://medium.com/dawandadev/canvas-based-blur-detection-with-javascript-8d9dc25cb7d5) to find out more. 4 | 5 | ## Setup 6 | 7 | 1. You need [Node.js](https://nodejs.org/). 8 | 1. Make sure [Cairo](http://cairographics.org/) is installed in your system, the [canvas](https://github.com/Automattic/node-canvas) module requires this. [How to install node-canvas](https://github.com/Automattic/node-canvas/wiki). 9 | * On OS X or macOS with [Homebrew](http://brew.sh/): `brew install pkg-config cairo pango libpng jpeg giflib librsvg` 10 | 1. `npm install` 11 | 12 | ## Usage 13 | 14 | Simply run `npm start path/to/image/file.jpg` 15 | 16 | ## Troubleshooting 17 | 18 | ### On macOS 12 Monterey: canvas.node can’t be opened 19 | 20 | Full error message: 21 | ``` 22 | “canvas.node” can’t be opened because Apple cannot check it for malicious software. 23 | 24 | This software needs to be updated. Contact the developer for more information. 25 | ``` 26 | 27 | * If this error pops up, click `Show in Finder`. 28 | * Right-click the file `canvas.node`, select `Open with` and choose `Other...`. 29 | * Make sure `Enable: All Applications` is selected. Pick `/Applications/Utilities/Terminal.app` (the default macOS Terminal). 30 | * Confirm that you really want to open it. 31 | * Run `npm start path/to/image/file.jpg` again - this time an `Open` button will be displayed in the dialog. 32 | * After choosing `Open` once, subsequent attempts of running the script will work without showing any dialog. 33 | 34 | This possibly happens only if you cloned this repo onto a Volume mounted from a disk image file (`.dmg`). The reason is macOS' Gatekeeper trying to protect you from running software from an "unidentified developer". -------------------------------------------------------------------------------- /demo/canvasFilters.js: -------------------------------------------------------------------------------- 1 | // this is just a copy of https://github.com/kig/canvasfilters/blob/master/filters.js 2 | // that has been modified to be an ES module. 3 | // TODO: somehow bundle the original npm package as an ES module 4 | 5 | const Filters = {}; 6 | 7 | if (typeof Float32Array == 'undefined') { 8 | Filters.getFloat32Array = Filters.getUint8Array = function (len) { 9 | if (len.length) { 10 | return len.slice(0); 11 | } 12 | return new Array(len); 13 | }; 14 | } else { 15 | Filters.getFloat32Array = function (len) { 16 | return new Float32Array(len); 17 | }; 18 | Filters.getUint8Array = function (len) { 19 | return new Uint8Array(len); 20 | }; 21 | } 22 | 23 | if (typeof document != 'undefined') { 24 | Filters.tmpCanvas = document.createElement('canvas'); 25 | Filters.tmpCtx = Filters.tmpCanvas.getContext('2d'); 26 | 27 | Filters.getPixels = function (img) { 28 | var c, ctx; 29 | if (img.getContext) { 30 | c = img; 31 | try { 32 | ctx = c.getContext('2d'); 33 | } catch (e) {} 34 | } 35 | if (!ctx) { 36 | c = this.getCanvas(img.width, img.height); 37 | ctx = c.getContext('2d'); 38 | ctx.drawImage(img, 0, 0); 39 | } 40 | return ctx.getImageData(0, 0, c.width, c.height); 41 | }; 42 | 43 | Filters.createImageData = function (w, h) { 44 | return this.tmpCtx.createImageData(w, h); 45 | }; 46 | 47 | Filters.getCanvas = function (w, h) { 48 | var c = document.createElement('canvas'); 49 | c.width = w; 50 | c.height = h; 51 | return c; 52 | }; 53 | 54 | Filters.filterImage = function (filter, image, var_args) { 55 | var args = [this.getPixels(image)]; 56 | for (var i = 2; i < arguments.length; i++) { 57 | args.push(arguments[i]); 58 | } 59 | return filter.apply(this, args); 60 | }; 61 | 62 | Filters.toCanvas = function (pixels) { 63 | var canvas = this.getCanvas(pixels.width, pixels.height); 64 | canvas.getContext('2d').putImageData(pixels, 0, 0); 65 | return canvas; 66 | }; 67 | 68 | Filters.toImageData = function (pixels) { 69 | return this.identity(pixels); 70 | }; 71 | } else { 72 | onmessage = function (e) { 73 | var ds = e.data; 74 | if (!ds.length) { 75 | ds = [ds]; 76 | } 77 | postMessage(Filters.runPipeline(ds)); 78 | }; 79 | 80 | Filters.createImageData = function (w, h) { 81 | return { width: w, height: h, data: this.getFloat32Array(w * h * 4) }; 82 | }; 83 | } 84 | 85 | Filters.runPipeline = function (ds) { 86 | var res = null; 87 | res = this[ds[0].name].apply(this, ds[0].args); 88 | for (var i = 1; i < ds.length; i++) { 89 | var d = ds[i]; 90 | var args = d.args.slice(0); 91 | args.unshift(res); 92 | res = this[d.name].apply(this, args); 93 | } 94 | return res; 95 | }; 96 | 97 | Filters.createImageDataFloat32 = function (w, h) { 98 | return { width: w, height: h, data: this.getFloat32Array(w * h * 4) }; 99 | }; 100 | 101 | Filters.identity = function (pixels, args) { 102 | var output = Filters.createImageData(pixels.width, pixels.height); 103 | var dst = output.data; 104 | var d = pixels.data; 105 | for (var i = 0; i < d.length; i++) { 106 | dst[i] = d[i]; 107 | } 108 | return output; 109 | }; 110 | 111 | Filters.horizontalFlip = function (pixels) { 112 | var output = Filters.createImageData(pixels.width, pixels.height); 113 | var w = pixels.width; 114 | var h = pixels.height; 115 | var dst = output.data; 116 | var d = pixels.data; 117 | for (var y = 0; y < h; y++) { 118 | for (var x = 0; x < w; x++) { 119 | var off = (y * w + x) * 4; 120 | var dstOff = (y * w + (w - x - 1)) * 4; 121 | dst[dstOff] = d[off]; 122 | dst[dstOff + 1] = d[off + 1]; 123 | dst[dstOff + 2] = d[off + 2]; 124 | dst[dstOff + 3] = d[off + 3]; 125 | } 126 | } 127 | return output; 128 | }; 129 | 130 | Filters.verticalFlip = function (pixels) { 131 | var output = Filters.createImageData(pixels.width, pixels.height); 132 | var w = pixels.width; 133 | var h = pixels.height; 134 | var dst = output.data; 135 | var d = pixels.data; 136 | for (var y = 0; y < h; y++) { 137 | for (var x = 0; x < w; x++) { 138 | var off = (y * w + x) * 4; 139 | var dstOff = ((h - y - 1) * w + x) * 4; 140 | dst[dstOff] = d[off]; 141 | dst[dstOff + 1] = d[off + 1]; 142 | dst[dstOff + 2] = d[off + 2]; 143 | dst[dstOff + 3] = d[off + 3]; 144 | } 145 | } 146 | return output; 147 | }; 148 | 149 | Filters.luminance = function (pixels, args) { 150 | var output = Filters.createImageData(pixels.width, pixels.height); 151 | var dst = output.data; 152 | var d = pixels.data; 153 | for (var i = 0; i < d.length; i += 4) { 154 | var r = d[i]; 155 | var g = d[i + 1]; 156 | var b = d[i + 2]; 157 | // CIE luminance for the RGB 158 | var v = 0.2126 * r + 0.7152 * g + 0.0722 * b; 159 | dst[i] = dst[i + 1] = dst[i + 2] = v; 160 | dst[i + 3] = d[i + 3]; 161 | } 162 | return output; 163 | }; 164 | 165 | Filters.grayscale = function (pixels, args) { 166 | var output = Filters.createImageData(pixels.width, pixels.height); 167 | var dst = output.data; 168 | var d = pixels.data; 169 | for (var i = 0; i < d.length; i += 4) { 170 | var r = d[i]; 171 | var g = d[i + 1]; 172 | var b = d[i + 2]; 173 | var v = 0.3 * r + 0.59 * g + 0.11 * b; 174 | dst[i] = dst[i + 1] = dst[i + 2] = v; 175 | dst[i + 3] = d[i + 3]; 176 | } 177 | return output; 178 | }; 179 | 180 | Filters.grayscaleAvg = function (pixels, args) { 181 | var output = Filters.createImageData(pixels.width, pixels.height); 182 | var dst = output.data; 183 | var d = pixels.data; 184 | var f = 1 / 3; 185 | for (var i = 0; i < d.length; i += 4) { 186 | var r = d[i]; 187 | var g = d[i + 1]; 188 | var b = d[i + 2]; 189 | var v = (r + g + b) * f; 190 | dst[i] = dst[i + 1] = dst[i + 2] = v; 191 | dst[i + 3] = d[i + 3]; 192 | } 193 | return output; 194 | }; 195 | 196 | Filters.threshold = function (pixels, threshold, high, low) { 197 | var output = Filters.createImageData(pixels.width, pixels.height); 198 | if (high == null) high = 255; 199 | if (low == null) low = 0; 200 | var d = pixels.data; 201 | var dst = output.data; 202 | for (var i = 0; i < d.length; i += 4) { 203 | var r = d[i]; 204 | var g = d[i + 1]; 205 | var b = d[i + 2]; 206 | var v = 0.3 * r + 0.59 * g + 0.11 * b >= threshold ? high : low; 207 | dst[i] = dst[i + 1] = dst[i + 2] = v; 208 | dst[i + 3] = d[i + 3]; 209 | } 210 | return output; 211 | }; 212 | 213 | Filters.invert = function (pixels) { 214 | var output = Filters.createImageData(pixels.width, pixels.height); 215 | var d = pixels.data; 216 | var dst = output.data; 217 | for (var i = 0; i < d.length; i += 4) { 218 | dst[i] = 255 - d[i]; 219 | dst[i + 1] = 255 - d[i + 1]; 220 | dst[i + 2] = 255 - d[i + 2]; 221 | dst[i + 3] = d[i + 3]; 222 | } 223 | return output; 224 | }; 225 | 226 | Filters.brightnessContrast = function (pixels, brightness, contrast) { 227 | var lut = this.brightnessContrastLUT(brightness, contrast); 228 | return this.applyLUT(pixels, { 229 | r: lut, 230 | g: lut, 231 | b: lut, 232 | a: this.identityLUT(), 233 | }); 234 | }; 235 | 236 | Filters.applyLUT = function (pixels, lut) { 237 | var output = Filters.createImageData(pixels.width, pixels.height); 238 | var d = pixels.data; 239 | var dst = output.data; 240 | var r = lut.r; 241 | var g = lut.g; 242 | var b = lut.b; 243 | var a = lut.a; 244 | for (var i = 0; i < d.length; i += 4) { 245 | dst[i] = r[d[i]]; 246 | dst[i + 1] = g[d[i + 1]]; 247 | dst[i + 2] = b[d[i + 2]]; 248 | dst[i + 3] = a[d[i + 3]]; 249 | } 250 | return output; 251 | }; 252 | 253 | Filters.createLUTFromCurve = function (points) { 254 | var lut = this.getUint8Array(256); 255 | var p = [0, 0]; 256 | for (var i = 0, j = 0; i < lut.length; i++) { 257 | while (j < points.length && points[j][0] < i) { 258 | p = points[j]; 259 | j++; 260 | } 261 | lut[i] = p[1]; 262 | } 263 | return lut; 264 | }; 265 | 266 | Filters.identityLUT = function () { 267 | var lut = this.getUint8Array(256); 268 | for (var i = 0; i < lut.length; i++) { 269 | lut[i] = i; 270 | } 271 | return lut; 272 | }; 273 | 274 | Filters.invertLUT = function () { 275 | var lut = this.getUint8Array(256); 276 | for (var i = 0; i < lut.length; i++) { 277 | lut[i] = 255 - i; 278 | } 279 | return lut; 280 | }; 281 | 282 | Filters.brightnessContrastLUT = function (brightness, contrast) { 283 | var lut = this.getUint8Array(256); 284 | var contrastAdjust = -128 * contrast + 128; 285 | var brightnessAdjust = 255 * brightness; 286 | var adjust = contrastAdjust + brightnessAdjust; 287 | for (var i = 0; i < lut.length; i++) { 288 | var c = i * contrast + adjust; 289 | lut[i] = c < 0 ? 0 : c > 255 ? 255 : c; 290 | } 291 | return lut; 292 | }; 293 | 294 | Filters.convolve = function (pixels, weights, opaque) { 295 | var side = Math.round(Math.sqrt(weights.length)); 296 | var halfSide = Math.floor(side / 2); 297 | 298 | var src = pixels.data; 299 | var sw = pixels.width; 300 | var sh = pixels.height; 301 | 302 | var w = sw; 303 | var h = sh; 304 | var output = Filters.createImageData(w, h); 305 | var dst = output.data; 306 | 307 | var alphaFac = opaque ? 1 : 0; 308 | 309 | for (var y = 0; y < h; y++) { 310 | for (var x = 0; x < w; x++) { 311 | var sy = y; 312 | var sx = x; 313 | var dstOff = (y * w + x) * 4; 314 | var r = 0, 315 | g = 0, 316 | b = 0, 317 | a = 0; 318 | for (var cy = 0; cy < side; cy++) { 319 | for (var cx = 0; cx < side; cx++) { 320 | var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide)); 321 | var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide)); 322 | var srcOff = (scy * sw + scx) * 4; 323 | var wt = weights[cy * side + cx]; 324 | r += src[srcOff] * wt; 325 | g += src[srcOff + 1] * wt; 326 | b += src[srcOff + 2] * wt; 327 | a += src[srcOff + 3] * wt; 328 | } 329 | } 330 | dst[dstOff] = r; 331 | dst[dstOff + 1] = g; 332 | dst[dstOff + 2] = b; 333 | dst[dstOff + 3] = a + alphaFac * (255 - a); 334 | } 335 | } 336 | return output; 337 | }; 338 | 339 | Filters.verticalConvolve = function (pixels, weightsVector, opaque) { 340 | var side = weightsVector.length; 341 | var halfSide = Math.floor(side / 2); 342 | 343 | var src = pixels.data; 344 | var sw = pixels.width; 345 | var sh = pixels.height; 346 | 347 | var w = sw; 348 | var h = sh; 349 | var output = Filters.createImageData(w, h); 350 | var dst = output.data; 351 | 352 | var alphaFac = opaque ? 1 : 0; 353 | 354 | for (var y = 0; y < h; y++) { 355 | for (var x = 0; x < w; x++) { 356 | var sy = y; 357 | var sx = x; 358 | var dstOff = (y * w + x) * 4; 359 | var r = 0, 360 | g = 0, 361 | b = 0, 362 | a = 0; 363 | for (var cy = 0; cy < side; cy++) { 364 | var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide)); 365 | var scx = sx; 366 | var srcOff = (scy * sw + scx) * 4; 367 | var wt = weightsVector[cy]; 368 | r += src[srcOff] * wt; 369 | g += src[srcOff + 1] * wt; 370 | b += src[srcOff + 2] * wt; 371 | a += src[srcOff + 3] * wt; 372 | } 373 | dst[dstOff] = r; 374 | dst[dstOff + 1] = g; 375 | dst[dstOff + 2] = b; 376 | dst[dstOff + 3] = a + alphaFac * (255 - a); 377 | } 378 | } 379 | return output; 380 | }; 381 | 382 | Filters.horizontalConvolve = function (pixels, weightsVector, opaque) { 383 | var side = weightsVector.length; 384 | var halfSide = Math.floor(side / 2); 385 | 386 | var src = pixels.data; 387 | var sw = pixels.width; 388 | var sh = pixels.height; 389 | 390 | var w = sw; 391 | var h = sh; 392 | var output = Filters.createImageData(w, h); 393 | var dst = output.data; 394 | 395 | var alphaFac = opaque ? 1 : 0; 396 | 397 | for (var y = 0; y < h; y++) { 398 | for (var x = 0; x < w; x++) { 399 | var sy = y; 400 | var sx = x; 401 | var dstOff = (y * w + x) * 4; 402 | var r = 0, 403 | g = 0, 404 | b = 0, 405 | a = 0; 406 | for (var cx = 0; cx < side; cx++) { 407 | var scy = sy; 408 | var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide)); 409 | var srcOff = (scy * sw + scx) * 4; 410 | var wt = weightsVector[cx]; 411 | r += src[srcOff] * wt; 412 | g += src[srcOff + 1] * wt; 413 | b += src[srcOff + 2] * wt; 414 | a += src[srcOff + 3] * wt; 415 | } 416 | dst[dstOff] = r; 417 | dst[dstOff + 1] = g; 418 | dst[dstOff + 2] = b; 419 | dst[dstOff + 3] = a + alphaFac * (255 - a); 420 | } 421 | } 422 | return output; 423 | }; 424 | 425 | Filters.separableConvolve = function ( 426 | pixels, 427 | horizWeights, 428 | vertWeights, 429 | opaque 430 | ) { 431 | return this.horizontalConvolve( 432 | this.verticalConvolveFloat32(pixels, vertWeights, opaque), 433 | horizWeights, 434 | opaque 435 | ); 436 | }; 437 | 438 | Filters.convolveFloat32 = function (pixels, weights, opaque) { 439 | var side = Math.round(Math.sqrt(weights.length)); 440 | var halfSide = Math.floor(side / 2); 441 | 442 | var src = pixels.data; 443 | var sw = pixels.width; 444 | var sh = pixels.height; 445 | 446 | var w = sw; 447 | var h = sh; 448 | var output = Filters.createImageDataFloat32(w, h); 449 | var dst = output.data; 450 | 451 | var alphaFac = opaque ? 1 : 0; 452 | 453 | for (var y = 0; y < h; y++) { 454 | for (var x = 0; x < w; x++) { 455 | var sy = y; 456 | var sx = x; 457 | var dstOff = (y * w + x) * 4; 458 | var r = 0, 459 | g = 0, 460 | b = 0, 461 | a = 0; 462 | for (var cy = 0; cy < side; cy++) { 463 | for (var cx = 0; cx < side; cx++) { 464 | var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide)); 465 | var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide)); 466 | var srcOff = (scy * sw + scx) * 4; 467 | var wt = weights[cy * side + cx]; 468 | r += src[srcOff] * wt; 469 | g += src[srcOff + 1] * wt; 470 | b += src[srcOff + 2] * wt; 471 | a += src[srcOff + 3] * wt; 472 | } 473 | } 474 | dst[dstOff] = r; 475 | dst[dstOff + 1] = g; 476 | dst[dstOff + 2] = b; 477 | dst[dstOff + 3] = a + alphaFac * (255 - a); 478 | } 479 | } 480 | return output; 481 | }; 482 | 483 | Filters.verticalConvolveFloat32 = function (pixels, weightsVector, opaque) { 484 | var side = weightsVector.length; 485 | var halfSide = Math.floor(side / 2); 486 | 487 | var src = pixels.data; 488 | var sw = pixels.width; 489 | var sh = pixels.height; 490 | 491 | var w = sw; 492 | var h = sh; 493 | var output = Filters.createImageDataFloat32(w, h); 494 | var dst = output.data; 495 | 496 | var alphaFac = opaque ? 1 : 0; 497 | 498 | for (var y = 0; y < h; y++) { 499 | for (var x = 0; x < w; x++) { 500 | var sy = y; 501 | var sx = x; 502 | var dstOff = (y * w + x) * 4; 503 | var r = 0, 504 | g = 0, 505 | b = 0, 506 | a = 0; 507 | for (var cy = 0; cy < side; cy++) { 508 | var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide)); 509 | var scx = sx; 510 | var srcOff = (scy * sw + scx) * 4; 511 | var wt = weightsVector[cy]; 512 | r += src[srcOff] * wt; 513 | g += src[srcOff + 1] * wt; 514 | b += src[srcOff + 2] * wt; 515 | a += src[srcOff + 3] * wt; 516 | } 517 | dst[dstOff] = r; 518 | dst[dstOff + 1] = g; 519 | dst[dstOff + 2] = b; 520 | dst[dstOff + 3] = a + alphaFac * (255 - a); 521 | } 522 | } 523 | return output; 524 | }; 525 | 526 | Filters.horizontalConvolveFloat32 = function (pixels, weightsVector, opaque) { 527 | var side = weightsVector.length; 528 | var halfSide = Math.floor(side / 2); 529 | 530 | var src = pixels.data; 531 | var sw = pixels.width; 532 | var sh = pixels.height; 533 | 534 | var w = sw; 535 | var h = sh; 536 | var output = Filters.createImageDataFloat32(w, h); 537 | var dst = output.data; 538 | 539 | var alphaFac = opaque ? 1 : 0; 540 | 541 | for (var y = 0; y < h; y++) { 542 | for (var x = 0; x < w; x++) { 543 | var sy = y; 544 | var sx = x; 545 | var dstOff = (y * w + x) * 4; 546 | var r = 0, 547 | g = 0, 548 | b = 0, 549 | a = 0; 550 | for (var cx = 0; cx < side; cx++) { 551 | var scy = sy; 552 | var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide)); 553 | var srcOff = (scy * sw + scx) * 4; 554 | var wt = weightsVector[cx]; 555 | r += src[srcOff] * wt; 556 | g += src[srcOff + 1] * wt; 557 | b += src[srcOff + 2] * wt; 558 | a += src[srcOff + 3] * wt; 559 | } 560 | dst[dstOff] = r; 561 | dst[dstOff + 1] = g; 562 | dst[dstOff + 2] = b; 563 | dst[dstOff + 3] = a + alphaFac * (255 - a); 564 | } 565 | } 566 | return output; 567 | }; 568 | 569 | Filters.separableConvolveFloat32 = function ( 570 | pixels, 571 | horizWeights, 572 | vertWeights, 573 | opaque 574 | ) { 575 | return this.horizontalConvolveFloat32( 576 | this.verticalConvolveFloat32(pixels, vertWeights, opaque), 577 | horizWeights, 578 | opaque 579 | ); 580 | }; 581 | 582 | Filters.gaussianBlur = function (pixels, diameter) { 583 | diameter = Math.abs(diameter); 584 | if (diameter <= 1) return Filters.identity(pixels); 585 | var radius = diameter / 2; 586 | var len = Math.ceil(diameter) + (1 - (Math.ceil(diameter) % 2)); 587 | var weights = this.getFloat32Array(len); 588 | var rho = (radius + 0.5) / 3; 589 | var rhoSq = rho * rho; 590 | var gaussianFactor = 1 / Math.sqrt(2 * Math.PI * rhoSq); 591 | var rhoFactor = -1 / (2 * rho * rho); 592 | var wsum = 0; 593 | var middle = Math.floor(len / 2); 594 | for (var i = 0; i < len; i++) { 595 | var x = i - middle; 596 | var gx = gaussianFactor * Math.exp(x * x * rhoFactor); 597 | weights[i] = gx; 598 | wsum += gx; 599 | } 600 | for (var i = 0; i < weights.length; i++) { 601 | weights[i] /= wsum; 602 | } 603 | return Filters.separableConvolve(pixels, weights, weights, false); 604 | }; 605 | 606 | Filters.laplaceKernel = Filters.getFloat32Array([ 607 | -1, -1, -1, -1, 8, -1, -1, -1, -1, 608 | ]); 609 | Filters.laplace = function (pixels) { 610 | return Filters.convolve(pixels, this.laplaceKernel, true); 611 | }; 612 | 613 | Filters.sobelSignVector = Filters.getFloat32Array([-1, 0, 1]); 614 | Filters.sobelScaleVector = Filters.getFloat32Array([1, 2, 1]); 615 | 616 | Filters.sobelVerticalGradient = function (px) { 617 | return this.separableConvolveFloat32( 618 | px, 619 | this.sobelSignVector, 620 | this.sobelScaleVector 621 | ); 622 | }; 623 | 624 | Filters.sobelHorizontalGradient = function (px) { 625 | return this.separableConvolveFloat32( 626 | px, 627 | this.sobelScaleVector, 628 | this.sobelSignVector 629 | ); 630 | }; 631 | 632 | Filters.sobelVectors = function (px) { 633 | var vertical = this.sobelVerticalGradient(px); 634 | var horizontal = this.sobelHorizontalGradient(px); 635 | var id = { 636 | width: vertical.width, 637 | height: vertical.height, 638 | data: this.getFloat32Array(vertical.width * vertical.height * 8), 639 | }; 640 | var vd = vertical.data; 641 | var hd = horizontal.data; 642 | var idd = id.data; 643 | for (var i = 0, j = 0; i < idd.length; i += 2, j++) { 644 | idd[i] = hd[j]; 645 | idd[i + 1] = vd[j]; 646 | } 647 | return id; 648 | }; 649 | 650 | Filters.sobel = function (px) { 651 | px = this.grayscale(px); 652 | var vertical = this.sobelVerticalGradient(px); 653 | var horizontal = this.sobelHorizontalGradient(px); 654 | var id = this.createImageData(vertical.width, vertical.height); 655 | for (var i = 0; i < id.data.length; i += 4) { 656 | var v = Math.abs(vertical.data[i]); 657 | id.data[i] = v; 658 | var h = Math.abs(horizontal.data[i]); 659 | id.data[i + 1] = h; 660 | id.data[i + 2] = (v + h) / 4; 661 | id.data[i + 3] = 255; 662 | } 663 | return id; 664 | }; 665 | 666 | Filters.bilinearSample = function (pixels, x, y, rgba) { 667 | var x1 = Math.floor(x); 668 | var x2 = Math.ceil(x); 669 | var y1 = Math.floor(y); 670 | var y2 = Math.ceil(y); 671 | var a = (x1 + pixels.width * y1) * 4; 672 | var b = (x2 + pixels.width * y1) * 4; 673 | var c = (x1 + pixels.width * y2) * 4; 674 | var d = (x2 + pixels.width * y2) * 4; 675 | var df = x - x1 + (y - y1); 676 | var cf = x2 - x + (y - y1); 677 | var bf = x - x1 + (y2 - y); 678 | var af = x2 - x + (y2 - y); 679 | var rsum = 1 / (af + bf + cf + df); 680 | af *= rsum; 681 | bf *= rsum; 682 | cf *= rsum; 683 | df *= rsum; 684 | var data = pixels.data; 685 | rgba[0] = data[a] * af + data[b] * bf + data[c] * cf + data[d] * df; 686 | rgba[1] = 687 | data[a + 1] * af + data[b + 1] * bf + data[c + 1] * cf + data[d + 1] * df; 688 | rgba[2] = 689 | data[a + 2] * af + data[b + 2] * bf + data[c + 2] * cf + data[d + 2] * df; 690 | rgba[3] = 691 | data[a + 3] * af + data[b + 3] * bf + data[c + 3] * cf + data[d + 3] * df; 692 | return rgba; 693 | }; 694 | 695 | Filters.distortSine = function (pixels, amount, yamount) { 696 | if (amount == null) amount = 0.5; 697 | if (yamount == null) yamount = amount; 698 | var output = this.createImageData(pixels.width, pixels.height); 699 | var dst = output.data; 700 | var d = pixels.data; 701 | var px = this.createImageData(1, 1).data; 702 | for (var y = 0; y < output.height; y++) { 703 | var sy = -Math.sin((y / (output.height - 1)) * Math.PI * 2); 704 | var srcY = y + (sy * yamount * output.height) / 4; 705 | srcY = Math.max(Math.min(srcY, output.height - 1), 0); 706 | 707 | for (var x = 0; x < output.width; x++) { 708 | var sx = -Math.sin((x / (output.width - 1)) * Math.PI * 2); 709 | var srcX = x + (sx * amount * output.width) / 4; 710 | srcX = Math.max(Math.min(srcX, output.width - 1), 0); 711 | 712 | var rgba = this.bilinearSample(pixels, srcX, srcY, px); 713 | 714 | var off = (y * output.width + x) * 4; 715 | dst[off] = rgba[0]; 716 | dst[off + 1] = rgba[1]; 717 | dst[off + 2] = rgba[2]; 718 | dst[off + 3] = rgba[3]; 719 | } 720 | } 721 | return output; 722 | }; 723 | 724 | Filters.darkenBlend = function (below, above) { 725 | var output = Filters.createImageData(below.width, below.height); 726 | var a = below.data; 727 | var b = above.data; 728 | var dst = output.data; 729 | var f = 1 / 255; 730 | for (var i = 0; i < a.length; i += 4) { 731 | dst[i] = a[i] < b[i] ? a[i] : b[i]; 732 | dst[i + 1] = a[i + 1] < b[i + 1] ? a[i + 1] : b[i + 1]; 733 | dst[i + 2] = a[i + 2] < b[i + 2] ? a[i + 2] : b[i + 2]; 734 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 735 | } 736 | return output; 737 | }; 738 | 739 | Filters.lightenBlend = function (below, above) { 740 | var output = Filters.createImageData(below.width, below.height); 741 | var a = below.data; 742 | var b = above.data; 743 | var dst = output.data; 744 | var f = 1 / 255; 745 | for (var i = 0; i < a.length; i += 4) { 746 | dst[i] = a[i] > b[i] ? a[i] : b[i]; 747 | dst[i + 1] = a[i + 1] > b[i + 1] ? a[i + 1] : b[i + 1]; 748 | dst[i + 2] = a[i + 2] > b[i + 2] ? a[i + 2] : b[i + 2]; 749 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 750 | } 751 | return output; 752 | }; 753 | 754 | Filters.multiplyBlend = function (below, above) { 755 | var output = Filters.createImageData(below.width, below.height); 756 | var a = below.data; 757 | var b = above.data; 758 | var dst = output.data; 759 | var f = 1 / 255; 760 | for (var i = 0; i < a.length; i += 4) { 761 | dst[i] = a[i] * b[i] * f; 762 | dst[i + 1] = a[i + 1] * b[i + 1] * f; 763 | dst[i + 2] = a[i + 2] * b[i + 2] * f; 764 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 765 | } 766 | return output; 767 | }; 768 | 769 | Filters.screenBlend = function (below, above) { 770 | var output = Filters.createImageData(below.width, below.height); 771 | var a = below.data; 772 | var b = above.data; 773 | var dst = output.data; 774 | var f = 1 / 255; 775 | for (var i = 0; i < a.length; i += 4) { 776 | dst[i] = a[i] + b[i] - a[i] * b[i] * f; 777 | dst[i + 1] = a[i + 1] + b[i + 1] - a[i + 1] * b[i + 1] * f; 778 | dst[i + 2] = a[i + 2] + b[i + 2] - a[i + 2] * b[i + 2] * f; 779 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 780 | } 781 | return output; 782 | }; 783 | 784 | Filters.addBlend = function (below, above) { 785 | var output = Filters.createImageData(below.width, below.height); 786 | var a = below.data; 787 | var b = above.data; 788 | var dst = output.data; 789 | var f = 1 / 255; 790 | for (var i = 0; i < a.length; i += 4) { 791 | dst[i] = a[i] + b[i]; 792 | dst[i + 1] = a[i + 1] + b[i + 1]; 793 | dst[i + 2] = a[i + 2] + b[i + 2]; 794 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 795 | } 796 | return output; 797 | }; 798 | 799 | Filters.subBlend = function (below, above) { 800 | var output = Filters.createImageData(below.width, below.height); 801 | var a = below.data; 802 | var b = above.data; 803 | var dst = output.data; 804 | var f = 1 / 255; 805 | for (var i = 0; i < a.length; i += 4) { 806 | dst[i] = a[i] + b[i] - 255; 807 | dst[i + 1] = a[i + 1] + b[i + 1] - 255; 808 | dst[i + 2] = a[i + 2] + b[i + 2] - 255; 809 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 810 | } 811 | return output; 812 | }; 813 | 814 | Filters.differenceBlend = function (below, above) { 815 | var output = Filters.createImageData(below.width, below.height); 816 | var a = below.data; 817 | var b = above.data; 818 | var dst = output.data; 819 | var f = 1 / 255; 820 | for (var i = 0; i < a.length; i += 4) { 821 | dst[i] = Math.abs(a[i] - b[i]); 822 | dst[i + 1] = Math.abs(a[i + 1] - b[i + 1]); 823 | dst[i + 2] = Math.abs(a[i + 2] - b[i + 2]); 824 | dst[i + 3] = a[i + 3] + (255 - a[i + 3]) * b[i + 3] * f; 825 | } 826 | return output; 827 | }; 828 | 829 | Filters.erode = function (pixels) { 830 | var src = pixels.data; 831 | var sw = pixels.width; 832 | var sh = pixels.height; 833 | 834 | var w = sw; 835 | var h = sh; 836 | var output = Filters.createImageData(w, h); 837 | var dst = output.data; 838 | 839 | for (var y = 0; y < h; y++) { 840 | for (var x = 0; x < w; x++) { 841 | var sy = y; 842 | var sx = x; 843 | var dstOff = (y * w + x) * 4; 844 | var srcOff = (sy * sw + sx) * 4; 845 | var v = 0; 846 | if (src[srcOff] == 0) { 847 | if ( 848 | src[(sy * sw + Math.max(0, sx - 1)) * 4] == 0 && 849 | src[(Math.max(0, sy - 1) * sw + sx) * 4] == 0 850 | ) { 851 | v = 255; 852 | } 853 | } else { 854 | v = 255; 855 | } 856 | dst[dstOff] = v; 857 | dst[dstOff + 1] = v; 858 | dst[dstOff + 2] = v; 859 | dst[dstOff + 3] = 255; 860 | } 861 | } 862 | return output; 863 | }; 864 | 865 | export default Filters; 866 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto-Thin'; 3 | src: url(data:font/truetype;charset=utf-8;base64,AAEAAAATAQAABAAwRkZUTV9rLAEAAAE8AAAAHEdERUYDCAHeAAABWAAAACxHUE9TCQ3fzwAAAYQAABNmR1NVQrI6tFIAABTsAAAAUE9TLzK4YiUNAAAVPAAAAGBjbWFwHmrQkgAAFZwAAAISY3Z0ICkeBkUAABewAAAAPGZwZ22LC3pBAAAX7AAACZFnYXNwAAAAEAAAIYAAAAAIZ2x5ZglY1HcAACGIAABWQGhlYWQCOTpdAAB3yAAAADZoaGVhDvAGLwAAeAAAAAAkaG10eLTSYeUAAHgkAAAD0GxvY2GfJ4tAAAB79AAAAeptYXhwAyQC4QAAfeAAAAAgbmFtZWErjskAAH4AAAAEBHBvc3QThUYKAACCBAAAAy1wcmVwCgOOoQAAhTQAAACOd2ViZseFUhcAAIXEAAAABgAAAAEAAAAAzD2izwAAAADE8BEuAAAAAM49eAMAAQAAAA4AAAAkAAAAAAACAAMAAQDrAAEA7ADsAAIA7QDzAAEABAAAAAIAAAABAAAACgAeACwAAURGTFQACAAEAAAAAP//AAEAAAABa2VybgAIAAAAAQAAAAEABAACAAAAAwAMAt4IXgABAnIABAAAAC4AZgBmAGwAegCIAI4AmAFaAWAAiAFmAXABigGoAboBzAIGAgwCXgJsAmwAbABsAGwAbABsAGwAegCOAI4AjgCOAIgAiACIAIgAiACIAcwCbAJsAcwAZgBmAGYAZgABAFwACwADACT/wwBZ/+8AXP/fAAMADv/mAEL/9ABi/+8AAQCK/98AAgBL/+4AXP/qADAAEf8WABP/FgAf/xYAJv/FADkAFABG/94ASP/rAEn/6wBK/+sATP/rAFT/6wBW/+sAV//mAFr/6gBb/+gAXv/oAIT/xQCF/8UAhv/FAIf/xQCI/8UAif/FAKT/3gCl/94Apv/eAKf/3gCo/94Aqf/eAKv/6wCs/+sArf/rAK7/6wCv/+sAtv/rALf/6wC4/+sAuf/rALr/6wC9/+oAvv/qAL//6gDA/+oAwf/oAMP/6ADG/+sA3f8WAOD/FgDi/xYAAQBc/8EAAQBc/6QAAgBZAA4Aiv+fAAYAOf/VADv/5AA8/+wAPv/dAKH/3QDH/90ABwBX/7UAXP/HAG/+uAB//ygAiv9NAKr/jgC8/6EABAAOABQAQgARAFf/4gBiABMABAAOAA8AQgAMAFf/6wBiAA4ADgAL/+IADgAUAA//zwBCABIAS//qAFf/2ABZ/+oAYgATAG//rgB//80Aiv+gAKr/wQC8/8AA4f/TAAEAXP/lABQABwAQAAwAEAAOABQAQgASAEj/6ABJ/+gASv/oAEz/6ABW/+gAYgATAKv/6ACs/+gArf/oAK7/6ACv/+gAxv/oANsAEADcABAA3gAQAN8AEAADAEsADwBZABEAXAARAAEASwANAAEALgAHAAwAJgAoACkAKgArADAAMQA0ADUANgA5ADsAPAA+AD8ASwBXAFsAXgCEAIUAhgCHAIgAiQCLAIwAjQCOAI8AlACWAJcAmACZAJoAoQDBAMMAxwDbANwA3gDfAAEFagAEAAAACQAcADIDNAOWA6wDxgUIBTYFUAAFADsAFAA8ABIAPgAWAKEAFgDHABYAwAAR/xYAEf8WABH/FgAR/xYAE/8WABP/FgAT/xYAE/8WAB//FgAf/xYAH/8WAB//FgAm/8UAJv/FACb/xQAm/8UAOQAUADkAFAA5ABQAOQAUAEb/3gBG/94ARv/eAEb/3gBI/+sASP/rAEj/6wBI/+sASf/rAEn/6wBJ/+sASf/rAEr/6wBK/+sASv/rAEr/6wBM/+sATP/rAEz/6wBM/+sAVP/rAFT/6wBU/+sAVP/rAFb/6wBW/+sAVv/rAFb/6wBX/+YAV//mAFf/5gBX/+YAWv/qAFr/6gBa/+oAWv/qAFv/6ABb/+gAW//oAFv/6ABe/+gAXv/oAF7/6ABe/+gAhP/FAIT/xQCE/8UAhP/FAIX/xQCF/8UAhf/FAIX/xQCG/8UAhv/FAIb/xQCG/8UAh//FAIf/xQCH/8UAh//FAIj/xQCI/8UAiP/FAIj/xQCJ/8UAif/FAIn/xQCJ/8UApP/eAKT/3gCk/94ApP/eAKX/3gCl/94Apf/eAKX/3gCm/94Apv/eAKb/3gCm/94Ap//eAKf/3gCn/94Ap//eAKj/3gCo/94AqP/eAKj/3gCp/94Aqf/eAKn/3gCp/94Aq//rAKv/6wCr/+sAq//rAKz/6wCs/+sArP/rAKz/6wCt/+sArf/rAK3/6wCt/+sArv/rAK7/6wCu/+sArv/rAK//6wCv/+sAr//rAK//6wC2/+sAtv/rALb/6wC2/+sAt//rALf/6wC3/+sAt//rALj/6wC4/+sAuP/rALj/6wC5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AAAAAAAAAAAAAgA6AAcABwAdAAwADAAdABEAEQAeABMAEwAeAB8AHwAeACcAJwABACgAKAAEACkAKQADACoAKgAFAC0ALgACAC8ALwAMADAAMAAJADEAMQAKADIAMwACADQANAADADUANQALADkAOQAGADoAOgAMADsAOwANADwAPAAQAD0APQAOAD4APgAPAD8APwARAEYARgATAEcARwAVAEgASAAUAEoASgAWAE0ATQAXAFIAUwAXAFQAVAAYAFUAVQAVAFcAVwAaAFsAWwAZAF0AXQAbAF4AXgAZAF8AXwAcAIsAiwAEAIwAjwAFAJAAkwACAJQAlAADAJUAlQACAJYAmgADAJ0AoAAMAKEAoQAPAKQAqQATAKsAqwAUAKwArwAWALUAtQAXALYAugAYAMEAwQAZAMIAwgAVAMMAwwAZAMcAxwAPANsA3AAdAN0A3QAeAN4A3wAdAOAA4AAeAOIA4gAeAAIAOAAHAAcABwAMAAwABwARABEAEwASABIAFwATABMAEwAfAB8AEwAmACYAEQAoACgABQAsACwABQAvAC8AHAA0ADQABQA2ADYABQA4ADgAGQA5ADkACgA6ADoABgA7ADsADQA8ADwACQA9AD0AEgA+AD4ADgA/AD8AFABGAEYAGgBIAEoAFQBMAEwAFQBSAFMAGABUAFQACABVAFUAGABWAFYAFQBYAFgAGwBaAFoACwBbAFsAAgBdAF0AFgBeAF4AAgBfAF8ADABxAHEAFwCEAIkAEQCLAIsABQCWAJoABQCcAJwABQCdAKAABgChAKEADgCkAKkAGgCrAK8AFQC1ALUAGAC2ALoACAC9AMAACwDBAMEAAgDDAMMAAgDFAMUABQDGAMYAFQDHAMcADgDZANoAFwDbANwABwDdAN0AEwDeAN8ABwDgAOAAEwDiAOIAEwACABkABwAHAAAADAAMAAEAEQARAAIAEwATAAMAHwAfAAQAJgAqAAUALQA1AAoAOQA/ABMARgBIABoASgBKAB0ATQBNAB4AUgBVAB8AVwBXACMAWwBbACQAXQBfACUAhACJACgAiwCaAC4AnQChAD4ApACpAEMAqwCvAEkAtQC6AE4AwQDDAFQAxwDHAFcA2wDgAFgA4gDiAF4AAAABAAAACgAeACwAAURGTFQACAAEAAAAAP//AAEAAAABbGlnYQAIAAAAAQAA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NIM/AV6of3WEA/npsgUgqNgAAAD//wBh/kQDpgROEiYASAAAEQcAfAFV//cACbEBAbj/97ArKwD//wBX/+sDuAXjEiYASgAAEQcARQDPAB8ACLECAbAfsCsrAAD//wBX/+sDuAXdEiYASgAAEQcAeAGRABcACLECAbAXsCsrAAD//wBX/+sDuAXfEiYASgAAEQcAyAClABwACLECAbAcsCsrAAD//wBX/+sDuAW+EiYASgAAEQYAbHUOAAixAgKwDrArK////9EAAAEIBc0SJgDEAAARBwBF/2oACQAIsQEBsAmwKysAAP//AJgAAAHEBccSJgDEAAARBgB4KwEACLEBAbABsCsr////2AAAAdAFyRImAMQAABEHAMj/QAAGAAixAQGwBrArKwAA////qwAAAgkFqRImAMQAABEHAGz/EP/5AAmxAQK4//mwKysAAAIAe//rBC8F7QAgAC0AOEA1JREQAwIDAUIgHx4bGhcWFRQACgFAAAEAAwIBA1sEAQICAFMAAAAVAEQiISknIS0iLSQmBRErARYSHQEUACMiADU0EjMyFhc3NgInBSclLgEnNx4BFyUXATISPQEuASMiBhUUFgM7bGr++cfH/v/t04nbNQQBbGT+mBwBVTZ9SRRVjToBBB3+GLPhK9eexsPbBStm/uu93PX+yQENxvEBCmZaBbMBA1vOMMMoQh0yIk0slTD6bgEX24BfifnIpPUAAAD//wCsAAADugXREiYAUwAAEQcAygChAAcACLEBAbAHsCsrAAD//wBX/+sECgXiEiYAVAAAEQcARQDeAB4ACLECAbAesCsrAAD//wBX/+sECgXcEiYAVAAAEQcAeAGgABYACLECAbAWsCsrAAD//wBX/+sECgXeEiYAVAAAEQcAyAC0ABsACLECAbAbsCsrAAD//wBX/+sECgXREiYAVAAAEQcAygClAAcACLECAbAHsCsrAAD//wBX/+sECgW9EiYAVAAAEQcAbACEAA0ACLECArANsCsrAAAAAwBJAPEELwR2AAMABwALACtAKAADAAIBAwJZAAEAAAUBAFkABQQEBU0ABQUEUQAEBQRFEREREREQBhUrASE1IQEjNTMRIzUzBC/8GgPm/khgYGBgAq40ARp6/Ht6AAAAAwBX/28ECgTDABkAJQAxAERAQR8JBgMEBSsWAgYEEwECBgNCAAEAAWoABAUGBQQGaAADAgNrAAUFAFMAAAAXQwAGBgJTAAICFQJEKiMYEycTIgcWKxM0ADMyFhc3MwceAR0BFAAjIiYnByM3LgE1MxQWFzMBLgEjIgIVITQmLwEBHgEzMhI1VwEH0j1xMlQ/Y19r/vnROmovUz9hZnE2YFcGAa0sYzi/5ANHW1AG/lQpXjS+5AIy7gEuHRqsy0fskyrv/tIYGKzJRPKagtk8A3IZHP7hx3jUPgH8kBYWARzLAAD//wCo/+sDuAXNEiYAWgAAEQcARQDcAAkACLEBAbAJsCsrAAD//wCo/+sDuAXHEiYAWgAAEQcAeAGeAAEACLEBAbABsCsrAAD//wCo/+sDuAXJEiYAWgAAEQcAyACyAAYACLEBAbAGsCsrAAD//wCo/+sDuAWpEiYAWgAAEQcAbACC//kACbEBArj/+bArKwD//wAr/ksDsAXHEiYAXgAAEQcAeAFcAAEACLEBAbABsCsrAAAAAgCq/mAD5wYYABEAHwAzQDAZGAsGBAUEAUIAAgIOQwAEBANTAAMDF0MABQUAUwAAABVDAAEBEQFEJSUjERMiBhUrARQCIyImJxEjETMRPgEzMhIRIzQCIyIGBxEeATMyEjUD59i+f701NjY2un+/2Te1q52uJSi8jquzAgn6/txmV/24B7j9YWNy/tH+/+EBGZ5z/gZsgAEI4AD//wAr/ksDsAWpEiYAXgAAEQYAbED5AAmxAQK4//mwKysAAAAAAQC1AAAA6wQ6AAMAEkAPAAEBD0MAAAANAEQREAIRKzMjETPrNjYEOgAAAAACAHD/6wb9BcUAFwAlAFFAThwBBAkbAQgHAkIABQAGBwUGWQAJCQJTAAICFEMABAQDUQADAwxDAAcHAFEAAAANQwoBCAgBUwABARUBRBoYIB0YJRolERERERIlIhALFyspAQ4BIyIAGQEQADMyFhchFSERIRUhESEFMjY3ES4BIyICFREUAAb9/MRnlk3n/uABHudQo1kDPPzAAtr9JgNA+3pJjDs8iU3T/AD/CgsBTAEJATABCQFMDAk2/Zg2/VoVBQcFVgYG/tbz/s7w/tEAAwBc/+sHKAROACgAPABFAEtASBABCAcmJQMDBQQCQgAIAAQFCARZCQEHBwJTAwECAhdDBgEFBQBTAQoCAAAVAEQBAENBPj06OC0rIyEYFxQSDgwHBQAoASgLDysFIiYnDgEjIgA9ATQAMzIWFz4BMzIWHQEhFBYdARQGBx4BMzI2NxcOAQEUEjMyNjcuAT0BNDY3LgEjIgIVJSE1NCYjIgYHBY2I5jU05ZrT/vgBB9Kc5jQz34235/zmAQYEHNmTeaM6HUC3+onkwaHXHwQDAwUe2qK/5AN6AubIoJfRHBWskJOpAS7vKu4BLquUkq3wylMDCgIqHjoeodBCQipHSQIdzP7l0KMWMBpAHTUZo9b+4cdFIaXb0qUAAP//ACQAAAShBvQSJgA+AAARBwBsAJ4BRAAJsQECuAFEsCsrAAABAJgE5AKQBcMACAAcQBkGAwADAAIBQgEBAAIAawACAgwCRBISEQMSKwEVIycHIzU3MwKQRrm3QtJQBPENuroRzgAAAAIAzASzAdkFxQALABcAG0AYAAIAAQIBVwADAwBTAAAAFANEJCQkIgQTKxM0NjMyFhUUBiMiJjcUFjMyNjU0JiMiBsxQODZPTjc4UDkuISAsLCAhLgU6OFNSOTlOTjkiLSwjIy8vAAAAAQBLBPwCygXKABMAJUAiEwACAkAKCQIAPwADAAADAFcAAQECUwACAgwBRCElISIEEysBFAYjIiYjIgYVJzQ2MzIWMzI2NQLKZkpMnDc0RTdkTEiiNTJHBcBSaH1KPQpQY3xPPgAAAAABAD0CVwITAo0AAwAAASE1IQIT/ioB1gJXNgAAAQA9AlcCEwKNAAMAAAEhNSECE/4qAdYCVzYAAAEAPQJXAhMCjQADAAABITUhAhP+KgHWAlc2AP//AKAC0QTfAwcQRgDqzQBTM0AA//8AggLRBbsDBxBHAOr/fgAAZmZAAAAAAAEAZARjANwGGAAFABlAFgMAAgEAAUIAAQABawAAAA4ARBIRAhErGwEzAxUjZEYyQjYFCQEP/vWqAAAAAAEAZARjANwGGAAFABlAFgMAAgABAUIAAAEAawABAQ4BRBIRAhErEwMjEzUz3EYyQjYFcv7xAQypAAAAAAEAZP9VAMgBDAAFABdAFAMAAgABAUIAAQABagAAAGESEQIRKzcDIxM1M8gyMi42ZP7xAQ2qAAD//wBkBGMBkAYYECYA2wAAEAcA2wC0AAD//wBkBGMBkgYYECYA3AAAEAcA3AC2AAAAAgBk/1cBfQENAAUACwAdQBoJBgMABAABAUIDAQEAAWoCAQAAYRISEhEEEys3AyMTNTMXAyMTNTPIMjItN7UyMi42Z/7wAQ2ppv7wAQ2pAAABAJsCIQHpA6UADQAXQBQAAAEBAE8AAAABUwABAAFHJSICESsTNDYzMhYdARQGIyImNZtaTExcW0xNWgMFRlpbRUZGWFhGAP//ANMAAAQaAIIQJgATJgAQJwATAaYAABAHABMDFwAAAAEAbAC1AdkDrAAGAB9AHAQDAAMAAQFCAAEAAAFNAAEBAFEAAAEARRMRAhErEwEjATUBM7MBJkb+2QEnRgIw/oUBchMBcgAAAAEAagCrAdYDrAAGACZAIwUCAQMAAQFCAgEBAAABTQIBAQEAUQAAAQBFAAAABgAGEwMQKxMBFQEjCQGwASb+2kYBJf7bA6z+hBP+jgF7AYYAAQBBAIMDNQUUAAMABrMCAAEoKzcnARduLQLHLYMfBHIfAAAAAQBS/+sDxAXFACsAT0BMHgEIBx8BBggIAQEACQECAQRCCQEGCgEFBAYFWQsBBAMBAAEEAFkACAgHUwAHBxRDAAEBAlMAAgIVAkQrKikoJyYlIxERERMlIxAMGCsBIRUUEjMyNjcVDgEjIgA9ASM1MzUjNTM1NAAzMhYXFS4BIyICHQEhFSEVIQOU/a/lwDlwMzRwONT++bu7u7sBBdQ2aj41cTjB4gJR/a8CUQIpJ9v++hEROQ8QASL1JzfeODj0ASMQDzoQE/7/3jo43gAAAAIAWgOXBCoFsAAPABcAPEA5BQACAwYBQgADBgAGAwBoCAEGBgJRCQQCAgIMQwcFAQMAAAJRCQQCAgIMAEQXFhERERERERETEgoYKwEnAyMDBxEjETMTMxMzESMBIxEjESM1IQPzBqkzrAY3S7UGt0U3/eeeOKoBgAU8Af5aAbQB/k0CGf48AcT95wHg/iAB4DkAAAABAKMC0QPmAwcAAwAXQBQAAQAAAU0AAQEAUQAAAQBFERACESsBITUhA+b8vQNDAtE2AAAAAQAAAAAEOAQ4AAMAABEhESEEOPvIBDj7yAAAAAIAQgAAA3QGLQAXABsAO0A4DAsCAQMBQgADAwJTAAICFkMFAQAAAVEIBAIBAQ9DBwkCBgYNBkQAABsaGRgAFwAXERMlIxERChUrMxEjNTM1NDYzMhYXBy4BIyIGHQEhFSERISMRM/68vKuYQ4hBCEJvUn6QAR7+4gJANjYEAzeeo7IbHjQdGpiHnjf7/QQ6//8AQgAAA3QGLRAmAEsAABAHAFEChQAA//8AQgAABfkGLRAmAEsAABAHAOwChQAA//8AQgAABfkGLRAmAEsAABAnAEsChQAAEAcAUQUKAAAAAgBSAAAC8wMhAAoADwAqQCcNAQAECAEBAAJCBQEAAwEBAgABWQAEBAJRAAICDQJEERIREREQBhUrJTMVIxUjNSE1ATMBIREnBwJDsLA3/kYBsz7+YgFnBiTxN7q6JAJD/dAB4QI8AAABAIH/9QKrAywAKgHKQA4LAQIBFAEHAB0BBgUDQkuwClBYQCwAAgEAAQIAaAAFBwYHBQZoAAMAAQIDAVsIAQAABwUAB1sABgYEUwAEBA0ERBtLsAxQWEAsAAIBAAECAGgABQcGBwUGaAADAAECAwFbCAEAAAcFAAdbAAYGBFMABAQVBEQbS7AQUFhALAACAQABAgBoAAUHBgcFBmgAAwABAgMBWwgBAAAHBQAHWwAGBgRTAAQEDQREG0uwElBYQCwAAgEAAQIAaAAFBwYHBQZoAAMAAQIDAVsIAQAABwUAB1sABgYEUwAEBBUERBtLsBRQWEAsAAIBAAECAGgABQcGBwUGaAADAAECAwFbCAEAAAcFAAdbAAYGBFMABAQNBEQbS7AXUFhALAACAQABAgBoAAUHBgcFBmgAAwABAgMBWwgBAAAHBQAHWwAGBgRTAAQEFQREG0uwGVBYQCwAAgEAAQIAaAAFBwYHBQZoAAMAAQIDAVsIAQAABwUAB1sABgYEUwAEBA0ERBtALAACAQABAgBoAAUHBgcFBmgAAwABAgMBWwgBAAAHBQAHWwAGBgRTAAQEFQREWVlZWVlZWUAWAQApJyMhHx4bGQ8NCgkHBQAqASoJDysBMjY1NCYjIgYVIycmNjMyFhUUBgceARUUBiMiJj8BMxQWMzI2NTQmKwE1AZZrZWloXHouAgaacnqOSEFKTZt7c6YFAjB9ZWp0b25yAbVRTElaW0oGXnhzaj1gGBVhS2t5dW4GTmRbUFZQOAAAAQByAAACmQMsABoAMEAtDgECAQFCAgEEAUEAAgEEAQIEaAADAAECAwFbAAQEAFEAAAANAEQXIxInEAUUKykBNQE+ATU0JiMiBhUjJyY2MzIWFRQGBwUXIQKZ/dkBR1M/YGBocDADBZR8c4VjW/73AgHbNwEsUGYzTltmUAZfiHNuRoVY6wYAAAEAUgAAAS4DLAAFABNAEAUEAwIEAEAAAAANAEQQARArISMRBzU3AS42ptwC6w44FwAAAAABAAAAAQAAryLKVF8PPPUAHwgAAAAAAM49eAMAAAAAzj14A/9j/hEHKAdJAAAACAACAAAAAAAAAAEAAAhi/a0AAAgA/2P/jQcoAAEAAAAAAAAAAAAAAAAAAAD0AuwARAgAAAAB6gAAAAAAAAAAAAAB6gAAAY0AkwIJAHgE8QBBBGEAfwX3AHIE3wBzAVQAeAJfAJQCbwAcA1gAHgR/AEkBfwByAl8APQG3AK0DDgAqBI8AjARhAM8EYQB7BGEAbQRhAFIEYQC8BGEAqgRhAE0EYQBiBGEAXwFsAJIBbABzBB0AUgR2AJMEHQByA30AYgduAIEExwAiBNMAyAUvAJAFPADIBI4AyASUAMgFfgCoBZ4AyAIWAPAEZQBgBRQAyAQhAMgG3ADIBakAyAVUAIUEzgDIBVQAhQU8AMMExwBpBMgANwVVALkExwAjBz8APQTHADUExwAkBMgAXgG3ALUDBgA5Abf/+ANRAFsDTf//AhkAZwQ5AFwEYQCqBA8AYQRhAHoECABXAoUAQgRhAHkEYQCsAaYAuQG9/2MDyQCsAaYAuQcsAKEEYQCsBGEAVwRhAKoEYQB6AqgArAP7AHACeQAdBGEAqAPSACsGDgBTA9IANwPSACsD0gBXApQASQGWALUClAAABYQAnAHqAAABlgCTBFYAhgR+AEEF3gByBLcAOgGOAKwExwB5A4oAmwaCAHsDggCUA4kAkQRNAHoCXwA9BooAcwMrAFwDDwCcBDUASAMnAHIDPwCBAf8AbQRhAKsDsgBjAdgAqwHqAFEB1wBPA5IAlAOBAIAF/QCNBacAjQZ1AKEDkABoBMcAIgTHACIExwAiBMcAIgTHACIExwAiBxgAMgUvAJAEjgDIBI4AyASOAMgEjgDIAhYADAIWANMCFgATAhb/5gVaAFoFqQDIBVQAhQVUAIUFVACFBVQAhQVUAIUEHABiBVQAhQVVALkFVQC5BVUAuQVVALkExwAkBMMAyASfAKQEOQBcBDkAXAQ5AFwEOQBcBDkAXAQ5AFwGxwBdBA8AYQQIAFcECABXBAgAVwQIAFcBlv/RAZYAmAGW/9gBlv+rBJ8AewRhAKwEYQBXBGEAVwRhAFcEYQBXBGEAVwSHAEkEYQBXBGEAqARhAKgEYQCoBGEAqAPSACsEYQCqA9IAKwGWALUHUQBwB4EAXATHACQDHACYApQAzAMyAEsDpAAAB0kAAAOkAAAHSQAAAm0AAAHSAAABNgAAATYAAADpAAABdQAAAGcAAAJfAD0CXwA9Al8APQWEAKAGiQCCAUAAZAFAAGQBLABkAfQAZAH2AGQB4QBkAnsAmwTnANMBdQAAAlwAbAJcAGoDiQBBAdIAAAQkAFIE3wBaBI8AowQ4AAAEKwBCBCsAQgawAEIGsABCA0YAUgM/AIEDFwByAekAUgAAACwALAAsACwALAAsAE4AeADsAXQB4gJmAoQCvgL4AyoDVgNwA4oDoAO2A/YEEARWBLgE7gVABaYF1gY2BpYGqAa6BtYG/AcYB2gIEghGCJoI7gkqCVgJgAnWCf4KFApGCngKlArOCvYLPgt2C8wMHgx4DJgMyAzuDTANYA2IDbIN1g3uDhIOOA5SDmwO1A8iD3IPwBAWEFgQuhDwERARThGCEZgR7BIkEmISsBL+EzITihPOFAQULBRoFJgU2hUEFUgVXhWiFeIV6hYaFpwW9BdiF64X1BhiGIAZCBmGGZwZvBnEGlAaaBqsGuQbKBuQG7Qb/hwkHDwcahyEHLwcyBzkHQAdHB2EHZYdqB26Hcwd3h3wHjgeSh5cHm4egB6SHqQeth7IHtofJh84H0ofXB9uH4Afkh+4ID4gUCBiIHQghiCYIOwhPCFOIWAhciGCIZIhpCJAIlIiZCJ2IogimCKqIroizCLeI0gjWiNsI34jkCOiI7Qj5CRWJGgkeiSMJJ4ksCT+JRAlJiWOJhwmLiZQJoQmuCa4JrgmuCa4JrgmuCa4JrgmuCa4JrgmxibUJuIm7Cb4JxYnNCdQJ1wnaCeQJ7QnxCfEJ+goECgiKCIoiijUKO4o/ClEKVApXClsKaAqwisGKyAAAAABAAAA9ABGAAUARQAEAAIAfACJAG4AAAE5AcoAAwABAAAAEgDeAAMAAQQJAAAAPgAAAAMAAQQJAAEAFgA+AAMAAQQJAAIADgBUAAMAAQQJAAMALgBiAAMAAQQJAAQAJgCQAAMAAQQJAAUAtAC2AAMAAQQJAAYAFgFqAAMAAQQJAAcAQAGAAAMAAQQJAAkADAHAAAMAAQQJAAsAFAHMAAMAAQQJAAwAJgHgAAMAAQQJAA0AXAIGAAMAAQQJAA4AVAJiAAMAAQQJABAADAK2AAMAAQQJABEACALCAAMAAQQJABIAFgLKAAMAAQQJAMgAFgLgAAMAAQQJAMkAMAL2AEYAbwBuAHQAIABkAGEAdABhACAAYwBvAHAAeQByAGkAZwBoAHQAIABHAG8AbwBnAGwAZQAgADIAMAAxADIAUgBvAGIAbwB0AG8AIABUAGgAaQBuAFIAZQBnAHUAbABhAHIARwBvAG8AZwBsAGUAOgBSAG8AYgBvAHQAbwAgAFQAaABpAG4AOgAyADAAMQAzAFIAbwBiAG8AdABvACAAVABoAGkAbgAgAFIAZQBnAHUAbABhAHIAVgBlAHIAcwBpAG8AbgAgADEALgAxADAAMAAxADQAMQA7ACAAMgAwADEAMwA7ACAAdAB0AGYAYQB1AHQAbwBoAGkAbgB0ACAAKAB2ADAALgA5ADQALgAxADQALQBjADkAMAAxACkAIAAtAGwAIAA4ACAALQByACAANQAwACAALQBHACAAMgAwADAAIAAtAHgAIAAxADQAIAAtAHcAIAAiAGcARwBEACIAIAAtAGMAIAAtAGYAUgBvAGIAbwB0AG8ALQBUAGgAaQBuAFIAbwBiAG8AdABvACAAaQBzACAAYQAgAHQAcgBhAGQAZQBtAGEAcgBrACAAbwBmACAARwBvAG8AZwBsAGUALgBHAG8AbwBnAGwAZQBHAG8AbwBnAGwAZQAuAGMAbwBtAEMAaAByAGkAcwB0AGkAYQBuACAAUgBvAGIAZQByAHQAcwBvAG4ATABpAGMAZQBuAHMAZQBkACAAdQBuAGQAZQByACAAdABoAGUAIABBAHAAYQBjAGgAZQAgAEwAaQBjAGUAbgBzAGUALAAgAFYAZQByAHMAaQBvAG4AIAAyAC4AMABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBwAGEAYwBoAGUALgBvAHIAZwAvAGwAaQBjAGUAbgBzAGUAcwAvAEwASQBDAEUATgBTAEUALQAyAC4AMABSAG8AYgBvAHQAbwBUAGgAaQBuAFIAbwBiAG8AdABvACAAVABoAGkAbgBXAGUAYgBmAG8AbgB0ACAAMQAuADAARgByAGkAIABBAHUAZwAgADIAMwAgADEANgA6ADMANQA6ADEANgAgADIAMAAxADMAAgAAAAAAAP9qAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAD0AAABAgEDAQQBBQADAAQABQAGAAcACAAJAAoACwAMAA0ADgAPABAAEQASABMAFAAVABYAFwAYABkAGgAbABwAHQAeAB8AIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwADEAMgAzADQANQA2ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBIAEkASgBLAEwATQBOAE8AUABRAFIAUwBUAFUAVgBXAFgAWQBaAFsAXABdAF4AXwBgAGEBBgCjAIQAhQC9AJYA6ACGAI4AiwCdAKkApAEHAIoA2gCDAJMBCAEJAI0BCgCIAMMA3gELAJ4AqgD1APQA9gCiAK0AyQDHAK4AYgBjAJAAZADLAGUAyADKAM8AzADNAM4A6QBmANMA0ADRAK8AZwDwAJEA1gDUANUAaADrAO0AiQBqAGkAawBtAGwAbgCgAG8AcQBwAHIAcwB1AHQAdgB3AOoAeAB6AHkAewB9AHwAuAChAH8AfgCAAIEA7ADuALoA1wCwALEAuwDYAN0A2QEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkAsgCzALYAtwDEALQAtQDFAIcAqwEaAL4AvwC8ARsBHACMAO8BHQEeAR8BIAEhASIBIwEkASUGZ2x5cGgxB3VuaTAwMEQHdW5pMDAwMgd1bmkwMDA5B3VuaTAwQTAHdW5pMDBBRAd1bmkwMEIyB3VuaTAwQjMHdW5pMDBCNQd1bmkwMEI5B3VuaTIwMDAHdW5pMjAwMQd1bmkyMDAyB3VuaTIwMDMHdW5pMjAwNAd1bmkyMDA1B3VuaTIwMDYHdW5pMjAwNwd1bmkyMDA4B3VuaTIwMDkHdW5pMjAwQQd1bmkyMDEwB3VuaTIwMTEKZmlndXJlZGFzaAd1bmkyMDJGB3VuaTIwNUYERXVybwd1bmlFMDAwB3VuaUZCMDEHdW5pRkIwMgd1bmlGQjAzB3VuaUZCMDQIZ2x5cGgyMjQIZ2x5cGgyMjUIZ2x5cGgyMjYIZ2x5cGgyMjcAAABLsMhSWLEBAY5ZuQgACABjILABI0SwAyNwsBdFICBLsA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbABRWMjYrACI0SyCwEGKrIMBgYqshQGBipZsgQoCUVSRLIMCAcqsQYDRLEkAYhRWLBAiFixBgNEsSYBiFFYuAQAiFixBgNEWVlZWbgB/4WwBI2xBQBEAAAAAVIXx4QAAA==) 4 | format('truetype'); 5 | } 6 | 7 | body { 8 | margin: 2rem; 9 | color: rgb(191, 201, 200); 10 | background-color: rgb(52, 52, 52); 11 | font-family: 'Roboto-Thin', sans-serif; 12 | font-size: 0.9rem; 13 | } 14 | 15 | a { 16 | color: rgb(106, 220, 157); 17 | } 18 | 19 | canvas[width] { 20 | border: 1px solid rgb(191, 201, 200); 21 | margin-bottom: 3rem; 22 | } 23 | 24 | @keyframes spinner { 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | .spinner:before { 31 | content: ''; 32 | box-sizing: border-box; 33 | position: absolute; 34 | top: 50%; 35 | left: 50%; 36 | width: 120px; 37 | height: 120px; 38 | margin-top: -10px; 39 | margin-left: -10px; 40 | border-radius: 50%; 41 | border: 2px solid #ccc; 42 | border-top-color: #333; 43 | animation: spinner 0.6s linear infinite; 44 | } 45 | 46 | #score { 47 | color: #ff0059; 48 | } 49 | 50 | #score #value { 51 | font-size: 2rem; 52 | font-weight: bold; 53 | margin-bottom: 0; 54 | } 55 | 56 | #score #explanation { 57 | margin-top: 0; 58 | font-size: 0.8rem; 59 | } 60 | 61 | #blur_score:empty, 62 | #calculation_time:empty { 63 | margin: 0; 64 | } 65 | 66 | table#details { 67 | border-collapse: collapse; 68 | } 69 | 70 | table#details:not(:empty) { 71 | margin-bottom: 1rem; 72 | } 73 | 74 | table#details td { 75 | border: 1px solid #5d5d5d; 76 | padding: 4px; 77 | } 78 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const readImageFile = (rawFile) => 3 | new Promise((resolve, reject) => { 4 | if (!rawFile) { 5 | return reject(); 6 | } 7 | 8 | const reader = new FileReader(); 9 | reader.onload = (readerEvent) => { 10 | const img = new Image(); 11 | img.onload = function () { 12 | resolve({ 13 | // NOTE: This is not an ImageData object! 14 | rawFile: rawFile, 15 | data: img, 16 | width: img.width, 17 | height: img.height, 18 | }); 19 | }; 20 | img.onerror = reject; 21 | img.src = readerEvent.target.result; 22 | }; 23 | reader.readAsDataURL(rawFile); 24 | }); 25 | 26 | const measureBlur = (imageData) => { 27 | const worker = new Worker('worker.js', { type: 'module' }); 28 | worker.onerror = (event) => { 29 | console.log('worker error:', event); 30 | }; 31 | worker.onmessage = (messageEvent) => { 32 | showScore(messageEvent.data.score); 33 | }; 34 | worker.postMessage({ imageData }); 35 | }; 36 | 37 | const blurScore = document.querySelector('#score #value'); 38 | const blurScoreExplanation = document.querySelector('#score #explanation'); 39 | const scoreDetails = document.querySelector('#details'); 40 | const spinner = document.querySelector('.spinner'); 41 | const canvas = document.querySelector('canvas'); 42 | let calculationTime; 43 | 44 | const showScore = (score) => { 45 | blurScore.innerHTML = 'Score: ' + score.avg_edge_width_perc.toFixed(2); 46 | document.querySelector('#calculation_time').innerHTML = 47 | 'Calculation time: ' + 48 | ((Date.now() - calculationTime) / 1000).toFixed(3) + 49 | ' sec'; 50 | let value; 51 | Object.keys(score).forEach((key) => { 52 | value = score[key]; 53 | scoreDetails.innerHTML += `${key}${value}`; 54 | }); 55 | blurScoreExplanation.style.display = 'block'; 56 | spinner.style.display = 'none'; 57 | }; 58 | 59 | const measureBlurAndShowScore = (changeFileInputEvent) => { 60 | spinner.style.display = 'block'; 61 | scoreDetails.innerHTML = ''; 62 | blurScoreExplanation.style.display = 'none'; 63 | calculationTime = Date.now(); 64 | readImageFile(changeFileInputEvent.target.files[0]).then((img) => { 65 | const context = canvas.getContext('2d'); 66 | 67 | canvas.width = img.width; 68 | canvas.height = img.height; 69 | context.drawImage(img.data, 0, 0); 70 | 71 | const canvasData = context.getImageData( 72 | 0, 73 | 0, 74 | canvas.width, 75 | canvas.height 76 | ); 77 | 78 | measureBlur(canvasData); 79 | }, console.error); 80 | }; 81 | 82 | document 83 | .querySelector('#upload_image') 84 | .addEventListener('change', measureBlurAndShowScore, false); 85 | })(); 86 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Inspector Bokeh demo 4 | 5 | 6 | 7 |

Inspector Bokeh demo

8 |

9 | Select an image to measure its blur. It will only be processed in your 10 | browser, not uploaded anywhere. Your browser needs to support ES2015 11 | modules in web workers. 12 |

13 |

14 | 15 |

16 | 17 |
18 |

19 | 24 |
25 |

26 |
27 | 28 |

29 | The used algorithm is loosely based on the 2002 paper 30 | "A no-reference perceptual blur metric" by Pina Marziliano, Frederic 32 | Dufaux, Stefan Winkler, and Touradj Ebrahimi. 34 |

35 |

36 | Visit the 37 | GitHub project 38 | to see the code, this 39 | blog post 43 | describing how it works, or the 44 | author's homepage. 45 |

46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /demo/measureBlur.js: -------------------------------------------------------------------------------- 1 | // This is just a copy of ../src/measureBlur.js that has been edited 2 | // to assume that canvasFilters is already an ES module 3 | // TODO: solve with bundling somehow 4 | 5 | import Filters from './canvasFilters.js'; 6 | 7 | /** 8 | * I forgot why exactly I was doing this. 9 | * It somehow improves edge detection to blur the image a bit beforehand. 10 | * But we don't want to do this for very small images. 11 | */ 12 | const BLUR_BEFORE_EDGE_DETECTION_MIN_WIDTH = 360; // pixels 13 | const BLUR_BEFORE_EDGE_DETECTION_DIAMETER = 5.0; // pixels 14 | 15 | /** 16 | * Only count edges that reach a certain intensity. 17 | * I forgot which unit this was. But it's not pixels. 18 | */ 19 | const MIN_EDGE_INTENSITY = 20; 20 | 21 | const detectEdges = (imageData) => { 22 | const preBlurredImageData = 23 | imageData.width >= BLUR_BEFORE_EDGE_DETECTION_MIN_WIDTH 24 | ? Filters.gaussianBlur(imageData, BLUR_BEFORE_EDGE_DETECTION_DIAMETER) 25 | : imageData; 26 | 27 | const greyscaled = Filters.luminance(preBlurredImageData); 28 | const sobelKernel = Filters.getFloat32Array([1, 0, -1, 2, 0, -2, 1, 0, -1]); 29 | return Filters.convolve(greyscaled, sobelKernel, true); 30 | }; 31 | 32 | /** 33 | * Reduce imageData from RGBA to only one channel (Y/luminance after conversion 34 | * to greyscale) since RGB all have the same values and Alpha was ignored. 35 | */ 36 | const reducedPixels = (imageData) => { 37 | const { data: pixels, width } = imageData; 38 | const rowLen = width * 4; 39 | let i, 40 | x, 41 | y, 42 | row, 43 | rows = []; 44 | 45 | for (y = 0; y < pixels.length; y += rowLen) { 46 | row = new Uint8ClampedArray(imageData.width); 47 | x = 0; 48 | for (i = y; i < y + rowLen; i += 4) { 49 | row[x] = pixels[i]; 50 | x += 1; 51 | } 52 | rows.push(row); 53 | } 54 | return rows; 55 | }; 56 | 57 | /** 58 | * @param pixels Array of Uint8ClampedArrays (row in original image) 59 | */ 60 | const detectBlur = (pixels) => { 61 | const width = pixels[0].length; 62 | const height = pixels.length; 63 | 64 | let x, 65 | y, 66 | value, 67 | oldValue, 68 | edgeStart, 69 | edgeWidth, 70 | bm, 71 | percWidth, 72 | numEdges = 0, 73 | sumEdgeWidths = 0; 74 | 75 | for (y = 0; y < height; y += 1) { 76 | // Reset edge marker, none found yet 77 | edgeStart = -1; 78 | for (x = 0; x < width; x += 1) { 79 | value = pixels[y][x]; 80 | // Edge is still open 81 | if (edgeStart >= 0 && x > edgeStart) { 82 | oldValue = pixels[y][x - 1]; 83 | // Value stopped increasing => edge ended 84 | if (value < oldValue) { 85 | // Only count edges that reach a certain intensity 86 | if (oldValue >= MIN_EDGE_INTENSITY) { 87 | edgeWidth = x - edgeStart - 1; 88 | numEdges += 1; 89 | sumEdgeWidths += edgeWidth; 90 | } 91 | edgeStart = -1; // Reset edge marker 92 | } 93 | } 94 | // Edge starts 95 | if (value == 0) { 96 | edgeStart = x; 97 | } 98 | } 99 | } 100 | 101 | if (numEdges === 0) { 102 | bm = 0; 103 | percWidth = 0; 104 | } else { 105 | bm = sumEdgeWidths / numEdges; 106 | percWidth = (bm / width) * 100; 107 | } 108 | 109 | return { 110 | width: width, 111 | height: height, 112 | num_edges: numEdges, 113 | avg_edge_width: bm, 114 | avg_edge_width_perc: percWidth, 115 | }; 116 | }; 117 | 118 | const measureBlur = (imageData) => 119 | detectBlur(reducedPixels(detectEdges(imageData))); 120 | 121 | export default measureBlur; 122 | -------------------------------------------------------------------------------- /demo/worker.js: -------------------------------------------------------------------------------- 1 | import measureBlur from './measureBlur.js'; 2 | 3 | onmessage = (messageEvent) => { 4 | postMessage({ 5 | score: measureBlur(messageEvent.data.imageData), 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inspector-bokeh", 3 | "version": "0.1.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "inspector-bokeh", 9 | "version": "0.1.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "canvas": "2.9.0", 13 | "canvasfilters": "git://github.com/kig/canvasfilters.git" 14 | }, 15 | "devDependencies": { 16 | "http-server": "^14.1.0", 17 | "prettier": "2.5.1" 18 | } 19 | }, 20 | "node_modules/@mapbox/node-pre-gyp": { 21 | "version": "1.0.8", 22 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", 23 | "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", 24 | "dependencies": { 25 | "detect-libc": "^1.0.3", 26 | "https-proxy-agent": "^5.0.0", 27 | "make-dir": "^3.1.0", 28 | "node-fetch": "^2.6.5", 29 | "nopt": "^5.0.0", 30 | "npmlog": "^5.0.1", 31 | "rimraf": "^3.0.2", 32 | "semver": "^7.3.5", 33 | "tar": "^6.1.11" 34 | }, 35 | "bin": { 36 | "node-pre-gyp": "bin/node-pre-gyp" 37 | } 38 | }, 39 | "node_modules/abbrev": { 40 | "version": "1.1.1", 41 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 42 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 43 | }, 44 | "node_modules/agent-base": { 45 | "version": "6.0.2", 46 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 47 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 48 | "dependencies": { 49 | "debug": "4" 50 | }, 51 | "engines": { 52 | "node": ">= 6.0.0" 53 | } 54 | }, 55 | "node_modules/ansi-regex": { 56 | "version": "5.0.1", 57 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 58 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 59 | "engines": { 60 | "node": ">=8" 61 | } 62 | }, 63 | "node_modules/ansi-styles": { 64 | "version": "4.3.0", 65 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 66 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 67 | "dev": true, 68 | "dependencies": { 69 | "color-convert": "^2.0.1" 70 | }, 71 | "engines": { 72 | "node": ">=8" 73 | }, 74 | "funding": { 75 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 76 | } 77 | }, 78 | "node_modules/aproba": { 79 | "version": "2.0.0", 80 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", 81 | "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" 82 | }, 83 | "node_modules/are-we-there-yet": { 84 | "version": "2.0.0", 85 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 86 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 87 | "dependencies": { 88 | "delegates": "^1.0.0", 89 | "readable-stream": "^3.6.0" 90 | }, 91 | "engines": { 92 | "node": ">=10" 93 | } 94 | }, 95 | "node_modules/async": { 96 | "version": "2.6.3", 97 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 98 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 99 | "dev": true, 100 | "dependencies": { 101 | "lodash": "^4.17.14" 102 | } 103 | }, 104 | "node_modules/balanced-match": { 105 | "version": "1.0.2", 106 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 107 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 108 | }, 109 | "node_modules/basic-auth": { 110 | "version": "2.0.1", 111 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 112 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 113 | "dev": true, 114 | "dependencies": { 115 | "safe-buffer": "5.1.2" 116 | }, 117 | "engines": { 118 | "node": ">= 0.8" 119 | } 120 | }, 121 | "node_modules/basic-auth/node_modules/safe-buffer": { 122 | "version": "5.1.2", 123 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 124 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 125 | "dev": true 126 | }, 127 | "node_modules/brace-expansion": { 128 | "version": "1.1.11", 129 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 130 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 131 | "dependencies": { 132 | "balanced-match": "^1.0.0", 133 | "concat-map": "0.0.1" 134 | } 135 | }, 136 | "node_modules/call-bind": { 137 | "version": "1.0.2", 138 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 139 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 140 | "dev": true, 141 | "dependencies": { 142 | "function-bind": "^1.1.1", 143 | "get-intrinsic": "^1.0.2" 144 | }, 145 | "funding": { 146 | "url": "https://github.com/sponsors/ljharb" 147 | } 148 | }, 149 | "node_modules/canvas": { 150 | "version": "2.9.0", 151 | "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.0.tgz", 152 | "integrity": "sha512-0l93g7uxp7rMyr7H+XRQ28A3ud0dKIUTIEkUe1Dxh4rjUYN7B93+SjC3r1PDKA18xcQN87OFGgUnyw7LSgNLSQ==", 153 | "hasInstallScript": true, 154 | "dependencies": { 155 | "@mapbox/node-pre-gyp": "^1.0.0", 156 | "nan": "^2.15.0", 157 | "simple-get": "^3.0.3" 158 | }, 159 | "engines": { 160 | "node": ">=6" 161 | } 162 | }, 163 | "node_modules/canvasfilters": { 164 | "version": "0.0.1", 165 | "resolved": "git+ssh://git@github.com/kig/canvasfilters.git#590154b8da04b6c85e07c03a8566d63a26449830" 166 | }, 167 | "node_modules/chalk": { 168 | "version": "4.1.2", 169 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 170 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 171 | "dev": true, 172 | "dependencies": { 173 | "ansi-styles": "^4.1.0", 174 | "supports-color": "^7.1.0" 175 | }, 176 | "engines": { 177 | "node": ">=10" 178 | }, 179 | "funding": { 180 | "url": "https://github.com/chalk/chalk?sponsor=1" 181 | } 182 | }, 183 | "node_modules/chownr": { 184 | "version": "2.0.0", 185 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 186 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", 187 | "engines": { 188 | "node": ">=10" 189 | } 190 | }, 191 | "node_modules/color-convert": { 192 | "version": "2.0.1", 193 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 194 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 195 | "dev": true, 196 | "dependencies": { 197 | "color-name": "~1.1.4" 198 | }, 199 | "engines": { 200 | "node": ">=7.0.0" 201 | } 202 | }, 203 | "node_modules/color-name": { 204 | "version": "1.1.4", 205 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 206 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 207 | "dev": true 208 | }, 209 | "node_modules/color-support": { 210 | "version": "1.1.3", 211 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 212 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", 213 | "bin": { 214 | "color-support": "bin.js" 215 | } 216 | }, 217 | "node_modules/concat-map": { 218 | "version": "0.0.1", 219 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 220 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 221 | }, 222 | "node_modules/console-control-strings": { 223 | "version": "1.1.0", 224 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 225 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 226 | }, 227 | "node_modules/corser": { 228 | "version": "2.0.1", 229 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 230 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", 231 | "dev": true, 232 | "engines": { 233 | "node": ">= 0.4.0" 234 | } 235 | }, 236 | "node_modules/debug": { 237 | "version": "4.3.3", 238 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 239 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 240 | "dependencies": { 241 | "ms": "2.1.2" 242 | }, 243 | "engines": { 244 | "node": ">=6.0" 245 | }, 246 | "peerDependenciesMeta": { 247 | "supports-color": { 248 | "optional": true 249 | } 250 | } 251 | }, 252 | "node_modules/decompress-response": { 253 | "version": "4.2.1", 254 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", 255 | "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", 256 | "dependencies": { 257 | "mimic-response": "^2.0.0" 258 | }, 259 | "engines": { 260 | "node": ">=8" 261 | } 262 | }, 263 | "node_modules/delegates": { 264 | "version": "1.0.0", 265 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 266 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 267 | }, 268 | "node_modules/detect-libc": { 269 | "version": "1.0.3", 270 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 271 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", 272 | "bin": { 273 | "detect-libc": "bin/detect-libc.js" 274 | }, 275 | "engines": { 276 | "node": ">=0.10" 277 | } 278 | }, 279 | "node_modules/emoji-regex": { 280 | "version": "8.0.0", 281 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 282 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 283 | }, 284 | "node_modules/eventemitter3": { 285 | "version": "4.0.7", 286 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 287 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 288 | "dev": true 289 | }, 290 | "node_modules/follow-redirects": { 291 | "version": "1.14.7", 292 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", 293 | "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", 294 | "dev": true, 295 | "funding": [ 296 | { 297 | "type": "individual", 298 | "url": "https://github.com/sponsors/RubenVerborgh" 299 | } 300 | ], 301 | "engines": { 302 | "node": ">=4.0" 303 | }, 304 | "peerDependenciesMeta": { 305 | "debug": { 306 | "optional": true 307 | } 308 | } 309 | }, 310 | "node_modules/fs-minipass": { 311 | "version": "2.1.0", 312 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 313 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 314 | "dependencies": { 315 | "minipass": "^3.0.0" 316 | }, 317 | "engines": { 318 | "node": ">= 8" 319 | } 320 | }, 321 | "node_modules/fs.realpath": { 322 | "version": "1.0.0", 323 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 324 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 325 | }, 326 | "node_modules/function-bind": { 327 | "version": "1.1.1", 328 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 329 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 330 | "dev": true 331 | }, 332 | "node_modules/gauge": { 333 | "version": "3.0.2", 334 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 335 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 336 | "dependencies": { 337 | "aproba": "^1.0.3 || ^2.0.0", 338 | "color-support": "^1.1.2", 339 | "console-control-strings": "^1.0.0", 340 | "has-unicode": "^2.0.1", 341 | "object-assign": "^4.1.1", 342 | "signal-exit": "^3.0.0", 343 | "string-width": "^4.2.3", 344 | "strip-ansi": "^6.0.1", 345 | "wide-align": "^1.1.2" 346 | }, 347 | "engines": { 348 | "node": ">=10" 349 | } 350 | }, 351 | "node_modules/get-intrinsic": { 352 | "version": "1.1.1", 353 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 354 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 355 | "dev": true, 356 | "dependencies": { 357 | "function-bind": "^1.1.1", 358 | "has": "^1.0.3", 359 | "has-symbols": "^1.0.1" 360 | }, 361 | "funding": { 362 | "url": "https://github.com/sponsors/ljharb" 363 | } 364 | }, 365 | "node_modules/glob": { 366 | "version": "7.2.0", 367 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 368 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 369 | "dependencies": { 370 | "fs.realpath": "^1.0.0", 371 | "inflight": "^1.0.4", 372 | "inherits": "2", 373 | "minimatch": "^3.0.4", 374 | "once": "^1.3.0", 375 | "path-is-absolute": "^1.0.0" 376 | }, 377 | "engines": { 378 | "node": "*" 379 | }, 380 | "funding": { 381 | "url": "https://github.com/sponsors/isaacs" 382 | } 383 | }, 384 | "node_modules/has": { 385 | "version": "1.0.3", 386 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 387 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 388 | "dev": true, 389 | "dependencies": { 390 | "function-bind": "^1.1.1" 391 | }, 392 | "engines": { 393 | "node": ">= 0.4.0" 394 | } 395 | }, 396 | "node_modules/has-flag": { 397 | "version": "4.0.0", 398 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 399 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 400 | "dev": true, 401 | "engines": { 402 | "node": ">=8" 403 | } 404 | }, 405 | "node_modules/has-symbols": { 406 | "version": "1.0.2", 407 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 408 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 409 | "dev": true, 410 | "engines": { 411 | "node": ">= 0.4" 412 | }, 413 | "funding": { 414 | "url": "https://github.com/sponsors/ljharb" 415 | } 416 | }, 417 | "node_modules/has-unicode": { 418 | "version": "2.0.1", 419 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 420 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 421 | }, 422 | "node_modules/he": { 423 | "version": "1.2.0", 424 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 425 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 426 | "dev": true, 427 | "bin": { 428 | "he": "bin/he" 429 | } 430 | }, 431 | "node_modules/html-encoding-sniffer": { 432 | "version": "3.0.0", 433 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 434 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 435 | "dev": true, 436 | "dependencies": { 437 | "whatwg-encoding": "^2.0.0" 438 | }, 439 | "engines": { 440 | "node": ">=12" 441 | } 442 | }, 443 | "node_modules/http-proxy": { 444 | "version": "1.18.1", 445 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 446 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 447 | "dev": true, 448 | "dependencies": { 449 | "eventemitter3": "^4.0.0", 450 | "follow-redirects": "^1.0.0", 451 | "requires-port": "^1.0.0" 452 | }, 453 | "engines": { 454 | "node": ">=8.0.0" 455 | } 456 | }, 457 | "node_modules/http-server": { 458 | "version": "14.1.0", 459 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz", 460 | "integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==", 461 | "dev": true, 462 | "dependencies": { 463 | "basic-auth": "^2.0.1", 464 | "chalk": "^4.1.2", 465 | "corser": "^2.0.1", 466 | "he": "^1.2.0", 467 | "html-encoding-sniffer": "^3.0.0", 468 | "http-proxy": "^1.18.1", 469 | "mime": "^1.6.0", 470 | "minimist": "^1.2.5", 471 | "opener": "^1.5.1", 472 | "portfinder": "^1.0.28", 473 | "secure-compare": "3.0.1", 474 | "union": "~0.5.0", 475 | "url-join": "^4.0.1" 476 | }, 477 | "bin": { 478 | "http-server": "bin/http-server" 479 | }, 480 | "engines": { 481 | "node": ">=12" 482 | } 483 | }, 484 | "node_modules/https-proxy-agent": { 485 | "version": "5.0.0", 486 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 487 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 488 | "dependencies": { 489 | "agent-base": "6", 490 | "debug": "4" 491 | }, 492 | "engines": { 493 | "node": ">= 6" 494 | } 495 | }, 496 | "node_modules/iconv-lite": { 497 | "version": "0.6.3", 498 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 499 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 500 | "dev": true, 501 | "dependencies": { 502 | "safer-buffer": ">= 2.1.2 < 3.0.0" 503 | }, 504 | "engines": { 505 | "node": ">=0.10.0" 506 | } 507 | }, 508 | "node_modules/inflight": { 509 | "version": "1.0.6", 510 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 511 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 512 | "dependencies": { 513 | "once": "^1.3.0", 514 | "wrappy": "1" 515 | } 516 | }, 517 | "node_modules/inherits": { 518 | "version": "2.0.4", 519 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 520 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 521 | }, 522 | "node_modules/is-fullwidth-code-point": { 523 | "version": "3.0.0", 524 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 525 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 526 | "engines": { 527 | "node": ">=8" 528 | } 529 | }, 530 | "node_modules/lodash": { 531 | "version": "4.17.21", 532 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 533 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 534 | "dev": true 535 | }, 536 | "node_modules/lru-cache": { 537 | "version": "6.0.0", 538 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 539 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 540 | "dependencies": { 541 | "yallist": "^4.0.0" 542 | }, 543 | "engines": { 544 | "node": ">=10" 545 | } 546 | }, 547 | "node_modules/make-dir": { 548 | "version": "3.1.0", 549 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 550 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 551 | "dependencies": { 552 | "semver": "^6.0.0" 553 | }, 554 | "engines": { 555 | "node": ">=8" 556 | }, 557 | "funding": { 558 | "url": "https://github.com/sponsors/sindresorhus" 559 | } 560 | }, 561 | "node_modules/make-dir/node_modules/semver": { 562 | "version": "6.3.0", 563 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 564 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 565 | "bin": { 566 | "semver": "bin/semver.js" 567 | } 568 | }, 569 | "node_modules/mime": { 570 | "version": "1.6.0", 571 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 572 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 573 | "dev": true, 574 | "bin": { 575 | "mime": "cli.js" 576 | }, 577 | "engines": { 578 | "node": ">=4" 579 | } 580 | }, 581 | "node_modules/mimic-response": { 582 | "version": "2.1.0", 583 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", 584 | "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", 585 | "engines": { 586 | "node": ">=8" 587 | }, 588 | "funding": { 589 | "url": "https://github.com/sponsors/sindresorhus" 590 | } 591 | }, 592 | "node_modules/minimatch": { 593 | "version": "3.0.4", 594 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 595 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 596 | "dependencies": { 597 | "brace-expansion": "^1.1.7" 598 | }, 599 | "engines": { 600 | "node": "*" 601 | } 602 | }, 603 | "node_modules/minimist": { 604 | "version": "1.2.5", 605 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 606 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 607 | "dev": true 608 | }, 609 | "node_modules/minipass": { 610 | "version": "3.1.6", 611 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", 612 | "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", 613 | "dependencies": { 614 | "yallist": "^4.0.0" 615 | }, 616 | "engines": { 617 | "node": ">=8" 618 | } 619 | }, 620 | "node_modules/minizlib": { 621 | "version": "2.1.2", 622 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 623 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 624 | "dependencies": { 625 | "minipass": "^3.0.0", 626 | "yallist": "^4.0.0" 627 | }, 628 | "engines": { 629 | "node": ">= 8" 630 | } 631 | }, 632 | "node_modules/mkdirp": { 633 | "version": "1.0.4", 634 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 635 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 636 | "bin": { 637 | "mkdirp": "bin/cmd.js" 638 | }, 639 | "engines": { 640 | "node": ">=10" 641 | } 642 | }, 643 | "node_modules/ms": { 644 | "version": "2.1.2", 645 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 646 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 647 | }, 648 | "node_modules/nan": { 649 | "version": "2.15.0", 650 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", 651 | "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" 652 | }, 653 | "node_modules/node-fetch": { 654 | "version": "2.6.7", 655 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 656 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 657 | "dependencies": { 658 | "whatwg-url": "^5.0.0" 659 | }, 660 | "engines": { 661 | "node": "4.x || >=6.0.0" 662 | }, 663 | "peerDependencies": { 664 | "encoding": "^0.1.0" 665 | }, 666 | "peerDependenciesMeta": { 667 | "encoding": { 668 | "optional": true 669 | } 670 | } 671 | }, 672 | "node_modules/nopt": { 673 | "version": "5.0.0", 674 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 675 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 676 | "dependencies": { 677 | "abbrev": "1" 678 | }, 679 | "bin": { 680 | "nopt": "bin/nopt.js" 681 | }, 682 | "engines": { 683 | "node": ">=6" 684 | } 685 | }, 686 | "node_modules/npmlog": { 687 | "version": "5.0.1", 688 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 689 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 690 | "dependencies": { 691 | "are-we-there-yet": "^2.0.0", 692 | "console-control-strings": "^1.1.0", 693 | "gauge": "^3.0.0", 694 | "set-blocking": "^2.0.0" 695 | } 696 | }, 697 | "node_modules/object-assign": { 698 | "version": "4.1.1", 699 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 700 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 701 | "engines": { 702 | "node": ">=0.10.0" 703 | } 704 | }, 705 | "node_modules/object-inspect": { 706 | "version": "1.12.0", 707 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", 708 | "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", 709 | "dev": true, 710 | "funding": { 711 | "url": "https://github.com/sponsors/ljharb" 712 | } 713 | }, 714 | "node_modules/once": { 715 | "version": "1.4.0", 716 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 717 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 718 | "dependencies": { 719 | "wrappy": "1" 720 | } 721 | }, 722 | "node_modules/opener": { 723 | "version": "1.5.2", 724 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 725 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 726 | "dev": true, 727 | "bin": { 728 | "opener": "bin/opener-bin.js" 729 | } 730 | }, 731 | "node_modules/path-is-absolute": { 732 | "version": "1.0.1", 733 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 734 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 735 | "engines": { 736 | "node": ">=0.10.0" 737 | } 738 | }, 739 | "node_modules/portfinder": { 740 | "version": "1.0.28", 741 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 742 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 743 | "dev": true, 744 | "dependencies": { 745 | "async": "^2.6.2", 746 | "debug": "^3.1.1", 747 | "mkdirp": "^0.5.5" 748 | }, 749 | "engines": { 750 | "node": ">= 0.12.0" 751 | } 752 | }, 753 | "node_modules/portfinder/node_modules/debug": { 754 | "version": "3.2.7", 755 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 756 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 757 | "dev": true, 758 | "dependencies": { 759 | "ms": "^2.1.1" 760 | } 761 | }, 762 | "node_modules/portfinder/node_modules/mkdirp": { 763 | "version": "0.5.5", 764 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 765 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 766 | "dev": true, 767 | "dependencies": { 768 | "minimist": "^1.2.5" 769 | }, 770 | "bin": { 771 | "mkdirp": "bin/cmd.js" 772 | } 773 | }, 774 | "node_modules/prettier": { 775 | "version": "2.5.1", 776 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", 777 | "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", 778 | "dev": true, 779 | "bin": { 780 | "prettier": "bin-prettier.js" 781 | }, 782 | "engines": { 783 | "node": ">=10.13.0" 784 | } 785 | }, 786 | "node_modules/qs": { 787 | "version": "6.10.3", 788 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 789 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 790 | "dev": true, 791 | "dependencies": { 792 | "side-channel": "^1.0.4" 793 | }, 794 | "engines": { 795 | "node": ">=0.6" 796 | }, 797 | "funding": { 798 | "url": "https://github.com/sponsors/ljharb" 799 | } 800 | }, 801 | "node_modules/readable-stream": { 802 | "version": "3.6.0", 803 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 804 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 805 | "dependencies": { 806 | "inherits": "^2.0.3", 807 | "string_decoder": "^1.1.1", 808 | "util-deprecate": "^1.0.1" 809 | }, 810 | "engines": { 811 | "node": ">= 6" 812 | } 813 | }, 814 | "node_modules/requires-port": { 815 | "version": "1.0.0", 816 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 817 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", 818 | "dev": true 819 | }, 820 | "node_modules/rimraf": { 821 | "version": "3.0.2", 822 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 823 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 824 | "dependencies": { 825 | "glob": "^7.1.3" 826 | }, 827 | "bin": { 828 | "rimraf": "bin.js" 829 | }, 830 | "funding": { 831 | "url": "https://github.com/sponsors/isaacs" 832 | } 833 | }, 834 | "node_modules/safe-buffer": { 835 | "version": "5.2.1", 836 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 837 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 838 | "funding": [ 839 | { 840 | "type": "github", 841 | "url": "https://github.com/sponsors/feross" 842 | }, 843 | { 844 | "type": "patreon", 845 | "url": "https://www.patreon.com/feross" 846 | }, 847 | { 848 | "type": "consulting", 849 | "url": "https://feross.org/support" 850 | } 851 | ] 852 | }, 853 | "node_modules/safer-buffer": { 854 | "version": "2.1.2", 855 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 856 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 857 | "dev": true 858 | }, 859 | "node_modules/secure-compare": { 860 | "version": "3.0.1", 861 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 862 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", 863 | "dev": true 864 | }, 865 | "node_modules/semver": { 866 | "version": "7.3.5", 867 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 868 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 869 | "dependencies": { 870 | "lru-cache": "^6.0.0" 871 | }, 872 | "bin": { 873 | "semver": "bin/semver.js" 874 | }, 875 | "engines": { 876 | "node": ">=10" 877 | } 878 | }, 879 | "node_modules/set-blocking": { 880 | "version": "2.0.0", 881 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 882 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 883 | }, 884 | "node_modules/side-channel": { 885 | "version": "1.0.4", 886 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 887 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 888 | "dev": true, 889 | "dependencies": { 890 | "call-bind": "^1.0.0", 891 | "get-intrinsic": "^1.0.2", 892 | "object-inspect": "^1.9.0" 893 | }, 894 | "funding": { 895 | "url": "https://github.com/sponsors/ljharb" 896 | } 897 | }, 898 | "node_modules/signal-exit": { 899 | "version": "3.0.6", 900 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", 901 | "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" 902 | }, 903 | "node_modules/simple-concat": { 904 | "version": "1.0.1", 905 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 906 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", 907 | "funding": [ 908 | { 909 | "type": "github", 910 | "url": "https://github.com/sponsors/feross" 911 | }, 912 | { 913 | "type": "patreon", 914 | "url": "https://www.patreon.com/feross" 915 | }, 916 | { 917 | "type": "consulting", 918 | "url": "https://feross.org/support" 919 | } 920 | ] 921 | }, 922 | "node_modules/simple-get": { 923 | "version": "3.1.0", 924 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", 925 | "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", 926 | "dependencies": { 927 | "decompress-response": "^4.2.0", 928 | "once": "^1.3.1", 929 | "simple-concat": "^1.0.0" 930 | } 931 | }, 932 | "node_modules/string_decoder": { 933 | "version": "1.3.0", 934 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 935 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 936 | "dependencies": { 937 | "safe-buffer": "~5.2.0" 938 | } 939 | }, 940 | "node_modules/string-width": { 941 | "version": "4.2.3", 942 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 943 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 944 | "dependencies": { 945 | "emoji-regex": "^8.0.0", 946 | "is-fullwidth-code-point": "^3.0.0", 947 | "strip-ansi": "^6.0.1" 948 | }, 949 | "engines": { 950 | "node": ">=8" 951 | } 952 | }, 953 | "node_modules/strip-ansi": { 954 | "version": "6.0.1", 955 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 956 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 957 | "dependencies": { 958 | "ansi-regex": "^5.0.1" 959 | }, 960 | "engines": { 961 | "node": ">=8" 962 | } 963 | }, 964 | "node_modules/supports-color": { 965 | "version": "7.2.0", 966 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 967 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 968 | "dev": true, 969 | "dependencies": { 970 | "has-flag": "^4.0.0" 971 | }, 972 | "engines": { 973 | "node": ">=8" 974 | } 975 | }, 976 | "node_modules/tar": { 977 | "version": "6.1.11", 978 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 979 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 980 | "dependencies": { 981 | "chownr": "^2.0.0", 982 | "fs-minipass": "^2.0.0", 983 | "minipass": "^3.0.0", 984 | "minizlib": "^2.1.1", 985 | "mkdirp": "^1.0.3", 986 | "yallist": "^4.0.0" 987 | }, 988 | "engines": { 989 | "node": ">= 10" 990 | } 991 | }, 992 | "node_modules/tr46": { 993 | "version": "0.0.3", 994 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 995 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 996 | }, 997 | "node_modules/union": { 998 | "version": "0.5.0", 999 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 1000 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 1001 | "dev": true, 1002 | "dependencies": { 1003 | "qs": "^6.4.0" 1004 | }, 1005 | "engines": { 1006 | "node": ">= 0.8.0" 1007 | } 1008 | }, 1009 | "node_modules/url-join": { 1010 | "version": "4.0.1", 1011 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", 1012 | "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", 1013 | "dev": true 1014 | }, 1015 | "node_modules/util-deprecate": { 1016 | "version": "1.0.2", 1017 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1018 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1019 | }, 1020 | "node_modules/webidl-conversions": { 1021 | "version": "3.0.1", 1022 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1023 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 1024 | }, 1025 | "node_modules/whatwg-encoding": { 1026 | "version": "2.0.0", 1027 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 1028 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 1029 | "dev": true, 1030 | "dependencies": { 1031 | "iconv-lite": "0.6.3" 1032 | }, 1033 | "engines": { 1034 | "node": ">=12" 1035 | } 1036 | }, 1037 | "node_modules/whatwg-url": { 1038 | "version": "5.0.0", 1039 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1040 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 1041 | "dependencies": { 1042 | "tr46": "~0.0.3", 1043 | "webidl-conversions": "^3.0.0" 1044 | } 1045 | }, 1046 | "node_modules/wide-align": { 1047 | "version": "1.1.5", 1048 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 1049 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 1050 | "dependencies": { 1051 | "string-width": "^1.0.2 || 2 || 3 || 4" 1052 | } 1053 | }, 1054 | "node_modules/wrappy": { 1055 | "version": "1.0.2", 1056 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1057 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1058 | }, 1059 | "node_modules/yallist": { 1060 | "version": "4.0.0", 1061 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1062 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1063 | } 1064 | }, 1065 | "dependencies": { 1066 | "@mapbox/node-pre-gyp": { 1067 | "version": "1.0.8", 1068 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", 1069 | "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", 1070 | "requires": { 1071 | "detect-libc": "^1.0.3", 1072 | "https-proxy-agent": "^5.0.0", 1073 | "make-dir": "^3.1.0", 1074 | "node-fetch": "^2.6.5", 1075 | "nopt": "^5.0.0", 1076 | "npmlog": "^5.0.1", 1077 | "rimraf": "^3.0.2", 1078 | "semver": "^7.3.5", 1079 | "tar": "^6.1.11" 1080 | } 1081 | }, 1082 | "abbrev": { 1083 | "version": "1.1.1", 1084 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 1085 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 1086 | }, 1087 | "agent-base": { 1088 | "version": "6.0.2", 1089 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 1090 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 1091 | "requires": { 1092 | "debug": "4" 1093 | } 1094 | }, 1095 | "ansi-regex": { 1096 | "version": "5.0.1", 1097 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1098 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 1099 | }, 1100 | "ansi-styles": { 1101 | "version": "4.3.0", 1102 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1103 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1104 | "dev": true, 1105 | "requires": { 1106 | "color-convert": "^2.0.1" 1107 | } 1108 | }, 1109 | "aproba": { 1110 | "version": "2.0.0", 1111 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", 1112 | "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" 1113 | }, 1114 | "are-we-there-yet": { 1115 | "version": "2.0.0", 1116 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 1117 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 1118 | "requires": { 1119 | "delegates": "^1.0.0", 1120 | "readable-stream": "^3.6.0" 1121 | } 1122 | }, 1123 | "async": { 1124 | "version": "2.6.3", 1125 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 1126 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 1127 | "dev": true, 1128 | "requires": { 1129 | "lodash": "^4.17.14" 1130 | } 1131 | }, 1132 | "balanced-match": { 1133 | "version": "1.0.2", 1134 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1135 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 1136 | }, 1137 | "basic-auth": { 1138 | "version": "2.0.1", 1139 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 1140 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 1141 | "dev": true, 1142 | "requires": { 1143 | "safe-buffer": "5.1.2" 1144 | }, 1145 | "dependencies": { 1146 | "safe-buffer": { 1147 | "version": "5.1.2", 1148 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1149 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1150 | "dev": true 1151 | } 1152 | } 1153 | }, 1154 | "brace-expansion": { 1155 | "version": "1.1.11", 1156 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1157 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1158 | "requires": { 1159 | "balanced-match": "^1.0.0", 1160 | "concat-map": "0.0.1" 1161 | } 1162 | }, 1163 | "call-bind": { 1164 | "version": "1.0.2", 1165 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 1166 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 1167 | "dev": true, 1168 | "requires": { 1169 | "function-bind": "^1.1.1", 1170 | "get-intrinsic": "^1.0.2" 1171 | } 1172 | }, 1173 | "canvas": { 1174 | "version": "2.9.0", 1175 | "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.0.tgz", 1176 | "integrity": "sha512-0l93g7uxp7rMyr7H+XRQ28A3ud0dKIUTIEkUe1Dxh4rjUYN7B93+SjC3r1PDKA18xcQN87OFGgUnyw7LSgNLSQ==", 1177 | "requires": { 1178 | "@mapbox/node-pre-gyp": "^1.0.0", 1179 | "nan": "^2.15.0", 1180 | "simple-get": "^3.0.3" 1181 | } 1182 | }, 1183 | "canvasfilters": { 1184 | "version": "git+ssh://git@github.com/kig/canvasfilters.git#590154b8da04b6c85e07c03a8566d63a26449830", 1185 | "from": "canvasfilters@git://github.com/kig/canvasfilters.git" 1186 | }, 1187 | "chalk": { 1188 | "version": "4.1.2", 1189 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1190 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1191 | "dev": true, 1192 | "requires": { 1193 | "ansi-styles": "^4.1.0", 1194 | "supports-color": "^7.1.0" 1195 | } 1196 | }, 1197 | "chownr": { 1198 | "version": "2.0.0", 1199 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 1200 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 1201 | }, 1202 | "color-convert": { 1203 | "version": "2.0.1", 1204 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1205 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1206 | "dev": true, 1207 | "requires": { 1208 | "color-name": "~1.1.4" 1209 | } 1210 | }, 1211 | "color-name": { 1212 | "version": "1.1.4", 1213 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1214 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1215 | "dev": true 1216 | }, 1217 | "color-support": { 1218 | "version": "1.1.3", 1219 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 1220 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" 1221 | }, 1222 | "concat-map": { 1223 | "version": "0.0.1", 1224 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1225 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 1226 | }, 1227 | "console-control-strings": { 1228 | "version": "1.1.0", 1229 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 1230 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 1231 | }, 1232 | "corser": { 1233 | "version": "2.0.1", 1234 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 1235 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", 1236 | "dev": true 1237 | }, 1238 | "debug": { 1239 | "version": "4.3.3", 1240 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1241 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1242 | "requires": { 1243 | "ms": "2.1.2" 1244 | } 1245 | }, 1246 | "decompress-response": { 1247 | "version": "4.2.1", 1248 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", 1249 | "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", 1250 | "requires": { 1251 | "mimic-response": "^2.0.0" 1252 | } 1253 | }, 1254 | "delegates": { 1255 | "version": "1.0.0", 1256 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 1257 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 1258 | }, 1259 | "detect-libc": { 1260 | "version": "1.0.3", 1261 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 1262 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 1263 | }, 1264 | "emoji-regex": { 1265 | "version": "8.0.0", 1266 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1267 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 1268 | }, 1269 | "eventemitter3": { 1270 | "version": "4.0.7", 1271 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 1272 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 1273 | "dev": true 1274 | }, 1275 | "follow-redirects": { 1276 | "version": "1.14.7", 1277 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", 1278 | "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", 1279 | "dev": true 1280 | }, 1281 | "fs-minipass": { 1282 | "version": "2.1.0", 1283 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 1284 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 1285 | "requires": { 1286 | "minipass": "^3.0.0" 1287 | } 1288 | }, 1289 | "fs.realpath": { 1290 | "version": "1.0.0", 1291 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1292 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 1293 | }, 1294 | "function-bind": { 1295 | "version": "1.1.1", 1296 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1297 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 1298 | "dev": true 1299 | }, 1300 | "gauge": { 1301 | "version": "3.0.2", 1302 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 1303 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 1304 | "requires": { 1305 | "aproba": "^1.0.3 || ^2.0.0", 1306 | "color-support": "^1.1.2", 1307 | "console-control-strings": "^1.0.0", 1308 | "has-unicode": "^2.0.1", 1309 | "object-assign": "^4.1.1", 1310 | "signal-exit": "^3.0.0", 1311 | "string-width": "^4.2.3", 1312 | "strip-ansi": "^6.0.1", 1313 | "wide-align": "^1.1.2" 1314 | } 1315 | }, 1316 | "get-intrinsic": { 1317 | "version": "1.1.1", 1318 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 1319 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 1320 | "dev": true, 1321 | "requires": { 1322 | "function-bind": "^1.1.1", 1323 | "has": "^1.0.3", 1324 | "has-symbols": "^1.0.1" 1325 | } 1326 | }, 1327 | "glob": { 1328 | "version": "7.2.0", 1329 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 1330 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 1331 | "requires": { 1332 | "fs.realpath": "^1.0.0", 1333 | "inflight": "^1.0.4", 1334 | "inherits": "2", 1335 | "minimatch": "^3.0.4", 1336 | "once": "^1.3.0", 1337 | "path-is-absolute": "^1.0.0" 1338 | } 1339 | }, 1340 | "has": { 1341 | "version": "1.0.3", 1342 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1343 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1344 | "dev": true, 1345 | "requires": { 1346 | "function-bind": "^1.1.1" 1347 | } 1348 | }, 1349 | "has-flag": { 1350 | "version": "4.0.0", 1351 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1352 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1353 | "dev": true 1354 | }, 1355 | "has-symbols": { 1356 | "version": "1.0.2", 1357 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 1358 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 1359 | "dev": true 1360 | }, 1361 | "has-unicode": { 1362 | "version": "2.0.1", 1363 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 1364 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 1365 | }, 1366 | "he": { 1367 | "version": "1.2.0", 1368 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 1369 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 1370 | "dev": true 1371 | }, 1372 | "html-encoding-sniffer": { 1373 | "version": "3.0.0", 1374 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 1375 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 1376 | "dev": true, 1377 | "requires": { 1378 | "whatwg-encoding": "^2.0.0" 1379 | } 1380 | }, 1381 | "http-proxy": { 1382 | "version": "1.18.1", 1383 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 1384 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 1385 | "dev": true, 1386 | "requires": { 1387 | "eventemitter3": "^4.0.0", 1388 | "follow-redirects": "^1.0.0", 1389 | "requires-port": "^1.0.0" 1390 | } 1391 | }, 1392 | "http-server": { 1393 | "version": "14.1.0", 1394 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.0.tgz", 1395 | "integrity": "sha512-5lYsIcZtf6pdR8tCtzAHTWrAveo4liUlJdWc7YafwK/maPgYHs+VNP6KpCClmUnSorJrARVMXqtT055zBv11Yg==", 1396 | "dev": true, 1397 | "requires": { 1398 | "basic-auth": "^2.0.1", 1399 | "chalk": "^4.1.2", 1400 | "corser": "^2.0.1", 1401 | "he": "^1.2.0", 1402 | "html-encoding-sniffer": "^3.0.0", 1403 | "http-proxy": "^1.18.1", 1404 | "mime": "^1.6.0", 1405 | "minimist": "^1.2.5", 1406 | "opener": "^1.5.1", 1407 | "portfinder": "^1.0.28", 1408 | "secure-compare": "3.0.1", 1409 | "union": "~0.5.0", 1410 | "url-join": "^4.0.1" 1411 | } 1412 | }, 1413 | "https-proxy-agent": { 1414 | "version": "5.0.0", 1415 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 1416 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 1417 | "requires": { 1418 | "agent-base": "6", 1419 | "debug": "4" 1420 | } 1421 | }, 1422 | "iconv-lite": { 1423 | "version": "0.6.3", 1424 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1425 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1426 | "dev": true, 1427 | "requires": { 1428 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1429 | } 1430 | }, 1431 | "inflight": { 1432 | "version": "1.0.6", 1433 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1434 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1435 | "requires": { 1436 | "once": "^1.3.0", 1437 | "wrappy": "1" 1438 | } 1439 | }, 1440 | "inherits": { 1441 | "version": "2.0.4", 1442 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1443 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1444 | }, 1445 | "is-fullwidth-code-point": { 1446 | "version": "3.0.0", 1447 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1448 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 1449 | }, 1450 | "lodash": { 1451 | "version": "4.17.21", 1452 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1453 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 1454 | "dev": true 1455 | }, 1456 | "lru-cache": { 1457 | "version": "6.0.0", 1458 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1459 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1460 | "requires": { 1461 | "yallist": "^4.0.0" 1462 | } 1463 | }, 1464 | "make-dir": { 1465 | "version": "3.1.0", 1466 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 1467 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 1468 | "requires": { 1469 | "semver": "^6.0.0" 1470 | }, 1471 | "dependencies": { 1472 | "semver": { 1473 | "version": "6.3.0", 1474 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 1475 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 1476 | } 1477 | } 1478 | }, 1479 | "mime": { 1480 | "version": "1.6.0", 1481 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1482 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1483 | "dev": true 1484 | }, 1485 | "mimic-response": { 1486 | "version": "2.1.0", 1487 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", 1488 | "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" 1489 | }, 1490 | "minimatch": { 1491 | "version": "3.0.4", 1492 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1493 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1494 | "requires": { 1495 | "brace-expansion": "^1.1.7" 1496 | } 1497 | }, 1498 | "minimist": { 1499 | "version": "1.2.5", 1500 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1501 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 1502 | "dev": true 1503 | }, 1504 | "minipass": { 1505 | "version": "3.1.6", 1506 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", 1507 | "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", 1508 | "requires": { 1509 | "yallist": "^4.0.0" 1510 | } 1511 | }, 1512 | "minizlib": { 1513 | "version": "2.1.2", 1514 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 1515 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 1516 | "requires": { 1517 | "minipass": "^3.0.0", 1518 | "yallist": "^4.0.0" 1519 | } 1520 | }, 1521 | "mkdirp": { 1522 | "version": "1.0.4", 1523 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 1524 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 1525 | }, 1526 | "ms": { 1527 | "version": "2.1.2", 1528 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1529 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1530 | }, 1531 | "nan": { 1532 | "version": "2.15.0", 1533 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", 1534 | "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" 1535 | }, 1536 | "node-fetch": { 1537 | "version": "2.6.7", 1538 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 1539 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 1540 | "requires": { 1541 | "whatwg-url": "^5.0.0" 1542 | } 1543 | }, 1544 | "nopt": { 1545 | "version": "5.0.0", 1546 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 1547 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 1548 | "requires": { 1549 | "abbrev": "1" 1550 | } 1551 | }, 1552 | "npmlog": { 1553 | "version": "5.0.1", 1554 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 1555 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 1556 | "requires": { 1557 | "are-we-there-yet": "^2.0.0", 1558 | "console-control-strings": "^1.1.0", 1559 | "gauge": "^3.0.0", 1560 | "set-blocking": "^2.0.0" 1561 | } 1562 | }, 1563 | "object-assign": { 1564 | "version": "4.1.1", 1565 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1566 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1567 | }, 1568 | "object-inspect": { 1569 | "version": "1.12.0", 1570 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", 1571 | "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", 1572 | "dev": true 1573 | }, 1574 | "once": { 1575 | "version": "1.4.0", 1576 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1577 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1578 | "requires": { 1579 | "wrappy": "1" 1580 | } 1581 | }, 1582 | "opener": { 1583 | "version": "1.5.2", 1584 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 1585 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 1586 | "dev": true 1587 | }, 1588 | "path-is-absolute": { 1589 | "version": "1.0.1", 1590 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1591 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1592 | }, 1593 | "portfinder": { 1594 | "version": "1.0.28", 1595 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 1596 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 1597 | "dev": true, 1598 | "requires": { 1599 | "async": "^2.6.2", 1600 | "debug": "^3.1.1", 1601 | "mkdirp": "^0.5.5" 1602 | }, 1603 | "dependencies": { 1604 | "debug": { 1605 | "version": "3.2.7", 1606 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 1607 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 1608 | "dev": true, 1609 | "requires": { 1610 | "ms": "^2.1.1" 1611 | } 1612 | }, 1613 | "mkdirp": { 1614 | "version": "0.5.5", 1615 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 1616 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 1617 | "dev": true, 1618 | "requires": { 1619 | "minimist": "^1.2.5" 1620 | } 1621 | } 1622 | } 1623 | }, 1624 | "prettier": { 1625 | "version": "2.5.1", 1626 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", 1627 | "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", 1628 | "dev": true 1629 | }, 1630 | "qs": { 1631 | "version": "6.10.3", 1632 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 1633 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 1634 | "dev": true, 1635 | "requires": { 1636 | "side-channel": "^1.0.4" 1637 | } 1638 | }, 1639 | "readable-stream": { 1640 | "version": "3.6.0", 1641 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1642 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1643 | "requires": { 1644 | "inherits": "^2.0.3", 1645 | "string_decoder": "^1.1.1", 1646 | "util-deprecate": "^1.0.1" 1647 | } 1648 | }, 1649 | "requires-port": { 1650 | "version": "1.0.0", 1651 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 1652 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", 1653 | "dev": true 1654 | }, 1655 | "rimraf": { 1656 | "version": "3.0.2", 1657 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1658 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1659 | "requires": { 1660 | "glob": "^7.1.3" 1661 | } 1662 | }, 1663 | "safe-buffer": { 1664 | "version": "5.2.1", 1665 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1666 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1667 | }, 1668 | "safer-buffer": { 1669 | "version": "2.1.2", 1670 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1671 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1672 | "dev": true 1673 | }, 1674 | "secure-compare": { 1675 | "version": "3.0.1", 1676 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 1677 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", 1678 | "dev": true 1679 | }, 1680 | "semver": { 1681 | "version": "7.3.5", 1682 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 1683 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 1684 | "requires": { 1685 | "lru-cache": "^6.0.0" 1686 | } 1687 | }, 1688 | "set-blocking": { 1689 | "version": "2.0.0", 1690 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1691 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1692 | }, 1693 | "side-channel": { 1694 | "version": "1.0.4", 1695 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1696 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1697 | "dev": true, 1698 | "requires": { 1699 | "call-bind": "^1.0.0", 1700 | "get-intrinsic": "^1.0.2", 1701 | "object-inspect": "^1.9.0" 1702 | } 1703 | }, 1704 | "signal-exit": { 1705 | "version": "3.0.6", 1706 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", 1707 | "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" 1708 | }, 1709 | "simple-concat": { 1710 | "version": "1.0.1", 1711 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 1712 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" 1713 | }, 1714 | "simple-get": { 1715 | "version": "3.1.0", 1716 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", 1717 | "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", 1718 | "requires": { 1719 | "decompress-response": "^4.2.0", 1720 | "once": "^1.3.1", 1721 | "simple-concat": "^1.0.0" 1722 | } 1723 | }, 1724 | "string_decoder": { 1725 | "version": "1.3.0", 1726 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1727 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1728 | "requires": { 1729 | "safe-buffer": "~5.2.0" 1730 | } 1731 | }, 1732 | "string-width": { 1733 | "version": "4.2.3", 1734 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1735 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1736 | "requires": { 1737 | "emoji-regex": "^8.0.0", 1738 | "is-fullwidth-code-point": "^3.0.0", 1739 | "strip-ansi": "^6.0.1" 1740 | } 1741 | }, 1742 | "strip-ansi": { 1743 | "version": "6.0.1", 1744 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1745 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1746 | "requires": { 1747 | "ansi-regex": "^5.0.1" 1748 | } 1749 | }, 1750 | "supports-color": { 1751 | "version": "7.2.0", 1752 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1753 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1754 | "dev": true, 1755 | "requires": { 1756 | "has-flag": "^4.0.0" 1757 | } 1758 | }, 1759 | "tar": { 1760 | "version": "6.1.11", 1761 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 1762 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 1763 | "requires": { 1764 | "chownr": "^2.0.0", 1765 | "fs-minipass": "^2.0.0", 1766 | "minipass": "^3.0.0", 1767 | "minizlib": "^2.1.1", 1768 | "mkdirp": "^1.0.3", 1769 | "yallist": "^4.0.0" 1770 | } 1771 | }, 1772 | "tr46": { 1773 | "version": "0.0.3", 1774 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1775 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 1776 | }, 1777 | "union": { 1778 | "version": "0.5.0", 1779 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 1780 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 1781 | "dev": true, 1782 | "requires": { 1783 | "qs": "^6.4.0" 1784 | } 1785 | }, 1786 | "url-join": { 1787 | "version": "4.0.1", 1788 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", 1789 | "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", 1790 | "dev": true 1791 | }, 1792 | "util-deprecate": { 1793 | "version": "1.0.2", 1794 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1795 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1796 | }, 1797 | "webidl-conversions": { 1798 | "version": "3.0.1", 1799 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1800 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 1801 | }, 1802 | "whatwg-encoding": { 1803 | "version": "2.0.0", 1804 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 1805 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 1806 | "dev": true, 1807 | "requires": { 1808 | "iconv-lite": "0.6.3" 1809 | } 1810 | }, 1811 | "whatwg-url": { 1812 | "version": "5.0.0", 1813 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1814 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 1815 | "requires": { 1816 | "tr46": "~0.0.3", 1817 | "webidl-conversions": "^3.0.0" 1818 | } 1819 | }, 1820 | "wide-align": { 1821 | "version": "1.1.5", 1822 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 1823 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 1824 | "requires": { 1825 | "string-width": "^1.0.2 || 2 || 3 || 4" 1826 | } 1827 | }, 1828 | "wrappy": { 1829 | "version": "1.0.2", 1830 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1831 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1832 | }, 1833 | "yallist": { 1834 | "version": "4.0.0", 1835 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1836 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1837 | } 1838 | } 1839 | } 1840 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inspector-bokeh", 3 | "description": "Experimental library to measure blur in images", 4 | "author": "Timo Taglieber ", 5 | "version": "1", 6 | "license": "MIT", 7 | "type": "module", 8 | "main": "src/measureBlur.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/timotgl/inspector-bokeh.git" 12 | }, 13 | "scripts": { 14 | "start": "node src/cli.js", 15 | "demo": "node ./node_modules/http-server/bin/http-server ./demo" 16 | }, 17 | "dependencies": { 18 | "canvas": "2.9.0", 19 | "canvasfilters": "git://github.com/kig/canvasfilters.git" 20 | }, 21 | "devDependencies": { 22 | "http-server": "14.1.0", 23 | "prettier": "2.5.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import canvasModule from 'canvas'; 2 | import measureBlur from './measureBlur.js'; 3 | 4 | if (process.argv.length < 3) { 5 | console.info('Usage: node cli.js path/to/image/file.jpg'); 6 | process.exit(0); 7 | } 8 | 9 | const { createCanvas, loadImage } = canvasModule; 10 | const image = await loadImage(process.argv[2]); 11 | const canvas = createCanvas(image.width, image.height); 12 | const context = canvas.getContext('2d'); 13 | context.drawImage(image, 0, 0); 14 | const stats = measureBlur( 15 | context.getImageData(0, 0, canvas.width, canvas.height) 16 | ); 17 | 18 | console.log('Blur score:', Number(stats.avg_edge_width_perc.toFixed(2))); 19 | console.log(stats); 20 | -------------------------------------------------------------------------------- /src/measureBlur.js: -------------------------------------------------------------------------------- 1 | import canvasFilters from 'canvasfilters'; 2 | 3 | const { Filters } = canvasFilters; 4 | 5 | /** 6 | * I forgot why exactly I was doing this. 7 | * It somehow improves edge detection to blur the image a bit beforehand. 8 | * But we don't want to do this for very small images. 9 | */ 10 | const BLUR_BEFORE_EDGE_DETECTION_MIN_WIDTH = 360; // pixels 11 | const BLUR_BEFORE_EDGE_DETECTION_DIAMETER = 5.0; // pixels 12 | 13 | /** 14 | * Only count edges that reach a certain intensity. 15 | * I forgot which unit this was. But it's not pixels. 16 | */ 17 | const MIN_EDGE_INTENSITY = 20; 18 | 19 | const detectEdges = (imageData) => { 20 | const preBlurredImageData = 21 | imageData.width >= BLUR_BEFORE_EDGE_DETECTION_MIN_WIDTH 22 | ? Filters.gaussianBlur(imageData, BLUR_BEFORE_EDGE_DETECTION_DIAMETER) 23 | : imageData; 24 | 25 | const greyscaled = Filters.luminance(preBlurredImageData); 26 | const sobelKernel = Filters.getFloat32Array([1, 0, -1, 2, 0, -2, 1, 0, -1]); 27 | return Filters.convolve(greyscaled, sobelKernel, true); 28 | }; 29 | 30 | /** 31 | * Reduce imageData from RGBA to only one channel (Y/luminance after conversion 32 | * to greyscale) since RGB all have the same values and Alpha was ignored. 33 | */ 34 | const reducedPixels = (imageData) => { 35 | const { data: pixels, width } = imageData; 36 | const rowLen = width * 4; 37 | let i, 38 | x, 39 | y, 40 | row, 41 | rows = []; 42 | 43 | for (y = 0; y < pixels.length; y += rowLen) { 44 | row = new Uint8ClampedArray(imageData.width); 45 | x = 0; 46 | for (i = y; i < y + rowLen; i += 4) { 47 | row[x] = pixels[i]; 48 | x += 1; 49 | } 50 | rows.push(row); 51 | } 52 | return rows; 53 | }; 54 | 55 | /** 56 | * @param pixels Array of Uint8ClampedArrays (row in original image) 57 | */ 58 | const detectBlur = (pixels) => { 59 | const width = pixels[0].length; 60 | const height = pixels.length; 61 | 62 | let x, 63 | y, 64 | value, 65 | oldValue, 66 | edgeStart, 67 | edgeWidth, 68 | bm, 69 | percWidth, 70 | numEdges = 0, 71 | sumEdgeWidths = 0; 72 | 73 | for (y = 0; y < height; y += 1) { 74 | // Reset edge marker, none found yet 75 | edgeStart = -1; 76 | for (x = 0; x < width; x += 1) { 77 | value = pixels[y][x]; 78 | // Edge is still open 79 | if (edgeStart >= 0 && x > edgeStart) { 80 | oldValue = pixels[y][x - 1]; 81 | // Value stopped increasing => edge ended 82 | if (value < oldValue) { 83 | // Only count edges that reach a certain intensity 84 | if (oldValue >= MIN_EDGE_INTENSITY) { 85 | edgeWidth = x - edgeStart - 1; 86 | numEdges += 1; 87 | sumEdgeWidths += edgeWidth; 88 | } 89 | edgeStart = -1; // Reset edge marker 90 | } 91 | } 92 | // Edge starts 93 | if (value == 0) { 94 | edgeStart = x; 95 | } 96 | } 97 | } 98 | 99 | if (numEdges === 0) { 100 | bm = 0; 101 | percWidth = 0; 102 | } else { 103 | bm = sumEdgeWidths / numEdges; 104 | percWidth = (bm / width) * 100; 105 | } 106 | 107 | return { 108 | width: width, 109 | height: height, 110 | num_edges: numEdges, 111 | avg_edge_width: bm, 112 | avg_edge_width_perc: percWidth, 113 | }; 114 | }; 115 | 116 | const measureBlur = (imageData) => 117 | detectBlur(reducedPixels(detectEdges(imageData))); 118 | 119 | export default measureBlur; 120 | --------------------------------------------------------------------------------