mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-11 21:58:35 +00:00
Add files via upload
This commit is contained in:
parent
0081e0ec39
commit
29e63296ac
248
thirdparty/StreamSaver.js
vendored
248
thirdparty/StreamSaver.js
vendored
@ -2,135 +2,34 @@
|
||||
|
||||
/* global chrome location ReadableStream define MessageChannel TransformStream */
|
||||
|
||||
;((name, definition) => {
|
||||
typeof module !== 'undefined'
|
||||
? module.exports = definition()
|
||||
: typeof define === 'function' && typeof define.amd === 'object'
|
||||
? define(definition)
|
||||
: this[name] = definition()
|
||||
})('streamSaver', () => {
|
||||
function streamSaverFunction(){
|
||||
'use strict'
|
||||
|
||||
const global = typeof window === 'object' ? window : this
|
||||
const global = typeof window === 'object' ? window : this;
|
||||
if (!global.HTMLElement) console.warn('streamsaver is meant to run on browsers main thread')
|
||||
|
||||
let mitmTransporter = null
|
||||
let supportsTransferable = false
|
||||
const test = fn => { try { fn() } catch (e) {} }
|
||||
const ponyfill = global.WebStreamsPolyfill || {}
|
||||
const isSecureContext = global.isSecureContext
|
||||
let mitmTransporter = null;
|
||||
let supportsTransferable = false;
|
||||
const test = fn => { try { fn() } catch (e) {} };
|
||||
const ponyfill = global.WebStreamsPolyfill || {};
|
||||
const isSecureContext = global.isSecureContext;
|
||||
|
||||
//console.log(ponyfill);
|
||||
//console.log(isSecureContext);
|
||||
|
||||
// TODO: Must come up with a real detection test (#69)
|
||||
let useBlobFallback = /constructor/i.test(global.HTMLElement) || !!global.safari || !!global.WebKitPoint
|
||||
let useBlobFallback = /constructor/i.test(global.HTMLElement) || !!global.safari || !!global.WebKitPoint;
|
||||
|
||||
//console.log(useBlobFallback);
|
||||
|
||||
const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style
|
||||
? 'iframe'
|
||||
: 'navigate'
|
||||
|
||||
const streamSaver = {
|
||||
createWriteStream,
|
||||
WritableStream: global.WritableStream || ponyfill.WritableStream,
|
||||
supported: true,
|
||||
version: { full: '2.0.7', major: 2, minor: 0, dot: 7 },
|
||||
mitm: './thirdparty/mitm.html?v=2'
|
||||
}
|
||||
|
||||
/**
|
||||
* create a hidden iframe and append it to the DOM (body)
|
||||
*
|
||||
* @param {string} src page to load
|
||||
* @return {HTMLIFrameElement} page to load
|
||||
*/
|
||||
function makeIframe (src) {
|
||||
if (!src) throw new Error('meh')
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.hidden = true
|
||||
iframe.src = src
|
||||
iframe.loaded = false
|
||||
iframe.name = 'iframe'
|
||||
iframe.isIframe = true
|
||||
iframe.postMessage = (...args) => iframe.contentWindow.postMessage(...args)
|
||||
iframe.addEventListener('load', () => {
|
||||
iframe.loaded = true
|
||||
}, { once: true })
|
||||
document.body.appendChild(iframe)
|
||||
return iframe
|
||||
}
|
||||
|
||||
/**
|
||||
* create a popup that simulates the basic things
|
||||
* of what a iframe can do
|
||||
*
|
||||
* @param {string} src page to load
|
||||
* @return {object} iframe like object
|
||||
*/
|
||||
function makePopup (src) {
|
||||
const options = 'width=200,height=100'
|
||||
const delegate = document.createDocumentFragment()
|
||||
const popup = {
|
||||
frame: global.open(src, 'popup', options),
|
||||
loaded: false,
|
||||
isIframe: false,
|
||||
isPopup: true,
|
||||
remove () { popup.frame.close() },
|
||||
addEventListener (...args) { delegate.addEventListener(...args) },
|
||||
dispatchEvent (...args) { delegate.dispatchEvent(...args) },
|
||||
removeEventListener (...args) { delegate.removeEventListener(...args) },
|
||||
postMessage (...args) { popup.frame.postMessage(...args) }
|
||||
}
|
||||
|
||||
const onReady = evt => {
|
||||
if (evt.source === popup.frame) {
|
||||
popup.loaded = true
|
||||
global.removeEventListener('message', onReady)
|
||||
popup.dispatchEvent(new Event('load'))
|
||||
}
|
||||
}
|
||||
|
||||
global.addEventListener('message', onReady)
|
||||
|
||||
return popup
|
||||
}
|
||||
|
||||
try {
|
||||
// We can't look for service worker since it may still work on http
|
||||
new Response(new ReadableStream())
|
||||
if (isSecureContext && !('serviceWorker' in navigator)) {
|
||||
useBlobFallback = true
|
||||
}
|
||||
} catch (err) {
|
||||
useBlobFallback = true
|
||||
}
|
||||
|
||||
test(() => {
|
||||
// Transferable stream was first enabled in chrome v73 behind a flag
|
||||
const { readable } = new TransformStream()
|
||||
const mc = new MessageChannel()
|
||||
mc.port1.postMessage(readable, [readable])
|
||||
mc.port1.close()
|
||||
mc.port2.close()
|
||||
supportsTransferable = true
|
||||
// Freeze TransformStream object (can only work with native)
|
||||
Object.defineProperty(streamSaver, 'TransformStream', {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: TransformStream
|
||||
})
|
||||
})
|
||||
|
||||
function loadTransporter () {
|
||||
if (!mitmTransporter) {
|
||||
mitmTransporter = isSecureContext
|
||||
? makeIframe(streamSaver.mitm)
|
||||
: makePopup(streamSaver.mitm)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename filename that should be used
|
||||
* @param {object} options [description]
|
||||
* @param {number} size deprecated
|
||||
* @return {WritableStream<Uint8Array>}
|
||||
*/
|
||||
: 'navigate';
|
||||
|
||||
//console.log(downloadStrategy);
|
||||
|
||||
function createWriteStream (filename, stopStream){
|
||||
//console.log("createWriteStream");
|
||||
let opts = {
|
||||
size: null,
|
||||
pathname: null,
|
||||
@ -200,6 +99,7 @@
|
||||
}
|
||||
|
||||
channel.port1.onmessage = evt => {
|
||||
console.log(evt);
|
||||
// Service worker sent us a link that we should open.
|
||||
if (evt.data.download) {
|
||||
// Special treatment for popup...
|
||||
@ -309,5 +209,109 @@
|
||||
}, opts.writableStrategy)
|
||||
}
|
||||
|
||||
const streamSaver = {
|
||||
createWriteStream,
|
||||
WritableStream: global.WritableStream || ponyfill.WritableStream,
|
||||
supported: true,
|
||||
version: { full: '2.0.7', major: 2, minor: 0, dot: 7 },
|
||||
mitm: './thirdparty/mitm.html?v=2'
|
||||
}
|
||||
|
||||
//console.log(streamSaver);
|
||||
|
||||
/**
|
||||
* create a hidden iframe and append it to the DOM (body)
|
||||
*
|
||||
* @param {string} src page to load
|
||||
* @return {HTMLIFrameElement} page to load
|
||||
*/
|
||||
function makeIframe (src) {
|
||||
if (!src) throw new Error('meh')
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.hidden = true
|
||||
iframe.src = src
|
||||
iframe.loaded = false
|
||||
iframe.name = 'iframe'
|
||||
iframe.isIframe = true
|
||||
iframe.postMessage = (...args) => iframe.contentWindow.postMessage(...args)
|
||||
iframe.addEventListener('load', () => {
|
||||
iframe.loaded = true
|
||||
}, { once: true })
|
||||
document.body.appendChild(iframe)
|
||||
return iframe
|
||||
}
|
||||
|
||||
/**
|
||||
* create a popup that simulates the basic things
|
||||
* of what a iframe can do
|
||||
*
|
||||
* @param {string} src page to load
|
||||
* @return {object} iframe like object
|
||||
*/
|
||||
function makePopup (src) {
|
||||
const options = 'width=200,height=100'
|
||||
const delegate = document.createDocumentFragment()
|
||||
const popup = {
|
||||
frame: global.open(src, 'popup', options),
|
||||
loaded: false,
|
||||
isIframe: false,
|
||||
isPopup: true,
|
||||
remove () { popup.frame.close() },
|
||||
addEventListener (...args) { delegate.addEventListener(...args) },
|
||||
dispatchEvent (...args) { delegate.dispatchEvent(...args) },
|
||||
removeEventListener (...args) { delegate.removeEventListener(...args) },
|
||||
postMessage (...args) { popup.frame.postMessage(...args) }
|
||||
}
|
||||
|
||||
const onReady = evt => {
|
||||
if (evt.source === popup.frame) {
|
||||
popup.loaded = true
|
||||
global.removeEventListener('message', onReady)
|
||||
popup.dispatchEvent(new Event('load'))
|
||||
}
|
||||
}
|
||||
|
||||
global.addEventListener('message', onReady)
|
||||
|
||||
return popup
|
||||
}
|
||||
|
||||
try {
|
||||
// We can't look for service worker since it may still work on http
|
||||
new Response(new ReadableStream())
|
||||
if (isSecureContext && !('serviceWorker' in navigator)) {
|
||||
useBlobFallback = true
|
||||
}
|
||||
} catch (err) {
|
||||
useBlobFallback = true
|
||||
}
|
||||
|
||||
//console.log("useBlobFallback: "+useBlobFallback);
|
||||
|
||||
test(() => {
|
||||
// Transferable stream was first enabled in chrome v73 behind a flag
|
||||
const { readable } = new TransformStream()
|
||||
const mc = new MessageChannel()
|
||||
mc.port1.postMessage(readable, [readable])
|
||||
mc.port1.close()
|
||||
mc.port2.close()
|
||||
supportsTransferable = true
|
||||
// Freeze TransformStream object (can only work with native)
|
||||
Object.defineProperty(streamSaver, 'TransformStream', {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: TransformStream
|
||||
})
|
||||
})
|
||||
|
||||
function loadTransporter () {
|
||||
if (!mitmTransporter) {
|
||||
mitmTransporter = isSecureContext
|
||||
? makeIframe(streamSaver.mitm)
|
||||
: makePopup(streamSaver.mitm)
|
||||
}
|
||||
}
|
||||
|
||||
return streamSaver
|
||||
})
|
||||
};
|
||||
var streamSaver = streamSaverFunction();
|
||||
253
thirdparty/canvasFilters.js
vendored
Normal file
253
thirdparty/canvasFilters.js
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
// Modified copy obtained from https://github.com/timotgl/inspector-bokeh/tree/main/demo - MIT Lic
|
||||
// Original file based on https://github.com/kig/canvasfilters/blob/master/filters.js
|
||||
// I reduced the modified code to a few core functions; standard convolve/blur matrix functions.
|
||||
|
||||
const Filters = {};
|
||||
|
||||
if (typeof Float32Array == 'undefined') { // good
|
||||
Filters.getFloat32Array = Filters.getUint8Array = function (len) {
|
||||
if (len.length) {
|
||||
return len.slice(0);
|
||||
}
|
||||
return new Array(len);
|
||||
};
|
||||
} else {
|
||||
Filters.getFloat32Array = function (len) {
|
||||
return new Float32Array(len);
|
||||
};
|
||||
Filters.getUint8Array = function (len) {
|
||||
return new Uint8Array(len);
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof document != 'undefined') {
|
||||
Filters.tmpCanvas = document.createElement('canvas');
|
||||
Filters.tmpCtx = Filters.tmpCanvas.getContext('2d');
|
||||
Filters.createImageData = function (w, h) {
|
||||
return this.tmpCtx.createImageData(w, h);
|
||||
};
|
||||
} else {
|
||||
onmessage = function (e) {
|
||||
var ds = e.data;
|
||||
if (!ds.length) {
|
||||
ds = [ds];
|
||||
}
|
||||
postMessage(Filters.runPipeline(ds));
|
||||
};
|
||||
Filters.createImageData = function (w, h) {
|
||||
return { width: w, height: h, data: this.getFloat32Array(w * h * 4) };
|
||||
};
|
||||
}
|
||||
|
||||
Filters.convolve = function (pixels, weights, opaque) { // good
|
||||
var side = Math.round(Math.sqrt(weights.length));
|
||||
var halfSide = Math.floor(side / 2);
|
||||
|
||||
var src = pixels.data;
|
||||
var sw = pixels.width;
|
||||
var sh = pixels.height;
|
||||
|
||||
var w = sw;
|
||||
var h = sh;
|
||||
var output = Filters.createImageData(w, h);
|
||||
var dst = output.data;
|
||||
|
||||
var alphaFac = opaque ? 1 : 0;
|
||||
|
||||
for (var y = 0; y < h; y++) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
var sy = y;
|
||||
var sx = x;
|
||||
var dstOff = (y * w + x) * 4;
|
||||
var r = 0,
|
||||
g = 0,
|
||||
b = 0,
|
||||
a = 0;
|
||||
for (var cy = 0; cy < side; cy++) {
|
||||
for (var cx = 0; cx < side; cx++) {
|
||||
var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide));
|
||||
var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide));
|
||||
var srcOff = (scy * sw + scx) * 4;
|
||||
var wt = weights[cy * side + cx];
|
||||
r += src[srcOff] * wt;
|
||||
g += src[srcOff + 1] * wt;
|
||||
b += src[srcOff + 2] * wt;
|
||||
a += src[srcOff + 3] * wt;
|
||||
}
|
||||
}
|
||||
dst[dstOff] = r;
|
||||
dst[dstOff + 1] = g;
|
||||
dst[dstOff + 2] = b;
|
||||
dst[dstOff + 3] = a + alphaFac * (255 - a);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
Filters.luminance = function (pixels, args) { // good
|
||||
var output = Filters.createImageData(pixels.width, pixels.height);
|
||||
var dst = output.data;
|
||||
var d = pixels.data;
|
||||
for (var i = 0; i < d.length; i += 4) {
|
||||
var r = d[i];
|
||||
var g = d[i + 1];
|
||||
var b = d[i + 2];
|
||||
// CIE luminance for the RGB
|
||||
var v = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
dst[i] = dst[i + 1] = dst[i + 2] = v;
|
||||
dst[i + 3] = d[i + 3];
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
Filters.runPipeline = function (ds) {
|
||||
var res = null;
|
||||
res = this[ds[0].name].apply(this, ds[0].args);
|
||||
for (var i = 1; i < ds.length; i++) {
|
||||
var d = ds[i];
|
||||
var args = d.args.slice(0);
|
||||
args.unshift(res);
|
||||
res = this[d.name].apply(this, args);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
Filters.identity = function (pixels, args) {
|
||||
var output = Filters.createImageData(pixels.width, pixels.height);
|
||||
var dst = output.data;
|
||||
var d = pixels.data;
|
||||
for (var i = 0; i < d.length; i++) {
|
||||
dst[i] = d[i];
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
Filters.horizontalConvolve = function (pixels, weightsVector, opaque) {
|
||||
var side = weightsVector.length;
|
||||
var halfSide = Math.floor(side / 2);
|
||||
|
||||
var src = pixels.data;
|
||||
var sw = pixels.width;
|
||||
var sh = pixels.height;
|
||||
|
||||
var w = sw;
|
||||
var h = sh;
|
||||
var output = Filters.createImageData(w, h);
|
||||
var dst = output.data;
|
||||
|
||||
var alphaFac = opaque ? 1 : 0;
|
||||
|
||||
for (var y = 0; y < h; y++) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
var sy = y;
|
||||
var sx = x;
|
||||
var dstOff = (y * w + x) * 4;
|
||||
var r = 0,
|
||||
g = 0,
|
||||
b = 0,
|
||||
a = 0;
|
||||
for (var cx = 0; cx < side; cx++) {
|
||||
var scy = sy;
|
||||
var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide));
|
||||
var srcOff = (scy * sw + scx) * 4;
|
||||
var wt = weightsVector[cx];
|
||||
r += src[srcOff] * wt;
|
||||
g += src[srcOff + 1] * wt;
|
||||
b += src[srcOff + 2] * wt;
|
||||
a += src[srcOff + 3] * wt;
|
||||
}
|
||||
dst[dstOff] = r;
|
||||
dst[dstOff + 1] = g;
|
||||
dst[dstOff + 2] = b;
|
||||
dst[dstOff + 3] = a + alphaFac * (255 - a);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
Filters.separableConvolve = function (
|
||||
pixels,
|
||||
horizWeights,
|
||||
vertWeights,
|
||||
opaque
|
||||
) {
|
||||
return this.horizontalConvolve(
|
||||
this.verticalConvolveFloat32(pixels, vertWeights, opaque),
|
||||
horizWeights,
|
||||
opaque
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Filters.gaussianBlur = function (pixels, diameter) { // good
|
||||
diameter = Math.abs(diameter);
|
||||
if (diameter <= 1) return Filters.identity(pixels);
|
||||
var radius = diameter / 2;
|
||||
var len = Math.ceil(diameter) + (1 - (Math.ceil(diameter) % 2));
|
||||
var weights = this.getFloat32Array(len);
|
||||
var rho = (radius + 0.5) / 3;
|
||||
var rhoSq = rho * rho;
|
||||
var gaussianFactor = 1 / Math.sqrt(2 * Math.PI * rhoSq);
|
||||
var rhoFactor = -1 / (2 * rho * rho);
|
||||
var wsum = 0;
|
||||
var middle = Math.floor(len / 2);
|
||||
for (var i = 0; i < len; i++) {
|
||||
var x = i - middle;
|
||||
var gx = gaussianFactor * Math.exp(x * x * rhoFactor);
|
||||
weights[i] = gx;
|
||||
wsum += gx;
|
||||
}
|
||||
for (var i = 0; i < weights.length; i++) {
|
||||
weights[i] /= wsum;
|
||||
}
|
||||
return Filters.separableConvolve(pixels, weights, weights, false);
|
||||
};
|
||||
|
||||
|
||||
Filters.verticalConvolveFloat32 = function (pixels, weightsVector, opaque) {
|
||||
var side = weightsVector.length;
|
||||
var halfSide = Math.floor(side / 2);
|
||||
|
||||
var src = pixels.data;
|
||||
var sw = pixels.width;
|
||||
var sh = pixels.height;
|
||||
|
||||
var w = sw;
|
||||
var h = sh;
|
||||
var output = { width: w, height: h, data: this.getFloat32Array(w * h * 4) };
|
||||
var dst = output.data;
|
||||
|
||||
var alphaFac = opaque ? 1 : 0;
|
||||
|
||||
for (var y = 0; y < h; y++) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
var sy = y;
|
||||
var sx = x;
|
||||
var dstOff = (y * w + x) * 4;
|
||||
var r = 0,
|
||||
g = 0,
|
||||
b = 0,
|
||||
a = 0;
|
||||
for (var cy = 0; cy < side; cy++) {
|
||||
var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide));
|
||||
var scx = sx;
|
||||
var srcOff = (scy * sw + scx) * 4;
|
||||
var wt = weightsVector[cy];
|
||||
r += src[srcOff] * wt;
|
||||
g += src[srcOff + 1] * wt;
|
||||
b += src[srcOff + 2] * wt;
|
||||
a += src[srcOff + 3] * wt;
|
||||
}
|
||||
dst[dstOff] = r;
|
||||
dst[dstOff + 1] = g;
|
||||
dst[dstOff + 2] = b;
|
||||
dst[dstOff + 3] = a + alphaFac * (255 - a);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
export default Filters;
|
||||
11
thirdparty/focus_worker.js
vendored
Normal file
11
thirdparty/focus_worker.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Part of Inspector Bokeh by @timotgl
|
||||
// MIT License - Copyright (c) 2016 Timo Taglieber <github@timotaglieber.de>
|
||||
// https://github.com/timotgl/inspector-bokeh
|
||||
|
||||
import measureBlur from './measureBlur.js';
|
||||
|
||||
onmessage = (messageEvent) => {
|
||||
postMessage({
|
||||
score: measureBlur(messageEvent.data.imageData),
|
||||
});
|
||||
};
|
||||
124
thirdparty/measureBlur.js
vendored
Normal file
124
thirdparty/measureBlur.js
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
// Inspector Bokeh by @timotgl
|
||||
// MIT License - Copyright (c) 2016 Timo Taglieber <github@timotaglieber.de>
|
||||
// https://github.com/timotgl/inspector-bokeh
|
||||
|
||||
// This is just a copy of ../src/measureBlur.js that has been edited
|
||||
// to assume that canvasFilters is already an ES module
|
||||
// TODO: solve with bundling somehow
|
||||
|
||||
import Filters from './canvasFilters.js';
|
||||
|
||||
/**
|
||||
* I forgot why exactly I was doing this.
|
||||
* It somehow improves edge detection to blur the image a bit beforehand.
|
||||
* But we don't want to do this for very small images.
|
||||
*/
|
||||
const BLUR_BEFORE_EDGE_DETECTION_MIN_WIDTH = 360; // pixels
|
||||
const BLUR_BEFORE_EDGE_DETECTION_DIAMETER = 5.0; // pixels
|
||||
|
||||
/**
|
||||
* Only count edges that reach a certain intensity.
|
||||
* I forgot which unit this was. But it's not pixels.
|
||||
*/
|
||||
const MIN_EDGE_INTENSITY = 20;
|
||||
|
||||
const detectEdges = (imageData) => {
|
||||
const preBlurredImageData =
|
||||
imageData.width >= BLUR_BEFORE_EDGE_DETECTION_MIN_WIDTH
|
||||
? Filters.gaussianBlur(imageData, BLUR_BEFORE_EDGE_DETECTION_DIAMETER)
|
||||
: imageData;
|
||||
|
||||
const greyscaled = Filters.luminance(preBlurredImageData);
|
||||
const sobelKernel = Filters.getFloat32Array([1, 0, -1, 2, 0, -2, 1, 0, -1]);
|
||||
return Filters.convolve(greyscaled, sobelKernel, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduce imageData from RGBA to only one channel (Y/luminance after conversion
|
||||
* to greyscale) since RGB all have the same values and Alpha was ignored.
|
||||
*/
|
||||
const reducedPixels = (imageData) => {
|
||||
const { data: pixels, width } = imageData;
|
||||
const rowLen = width * 4;
|
||||
let i,
|
||||
x,
|
||||
y,
|
||||
row,
|
||||
rows = [];
|
||||
|
||||
for (y = 0; y < pixels.length; y += rowLen) {
|
||||
row = new Uint8ClampedArray(imageData.width);
|
||||
x = 0;
|
||||
for (i = y; i < y + rowLen; i += 4) {
|
||||
row[x] = pixels[i];
|
||||
x += 1;
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param pixels Array of Uint8ClampedArrays (row in original image)
|
||||
*/
|
||||
const detectBlur = (pixels) => {
|
||||
const width = pixels[0].length;
|
||||
const height = pixels.length;
|
||||
|
||||
let x,
|
||||
y,
|
||||
value,
|
||||
oldValue,
|
||||
edgeStart,
|
||||
edgeWidth,
|
||||
bm,
|
||||
percWidth,
|
||||
numEdges = 0,
|
||||
sumEdgeWidths = 0;
|
||||
|
||||
for (y = 0; y < height; y += 1) {
|
||||
// Reset edge marker, none found yet
|
||||
edgeStart = -1;
|
||||
for (x = 0; x < width; x += 1) {
|
||||
value = pixels[y][x];
|
||||
// Edge is still open
|
||||
if (edgeStart >= 0 && x > edgeStart) {
|
||||
oldValue = pixels[y][x - 1];
|
||||
// Value stopped increasing => edge ended
|
||||
if (value < oldValue) {
|
||||
// Only count edges that reach a certain intensity
|
||||
if (oldValue >= MIN_EDGE_INTENSITY) {
|
||||
edgeWidth = x - edgeStart - 1;
|
||||
numEdges += 1;
|
||||
sumEdgeWidths += edgeWidth;
|
||||
}
|
||||
edgeStart = -1; // Reset edge marker
|
||||
}
|
||||
}
|
||||
// Edge starts
|
||||
if (value == 0) {
|
||||
edgeStart = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numEdges === 0) {
|
||||
bm = 0;
|
||||
percWidth = 0;
|
||||
} else {
|
||||
bm = sumEdgeWidths / numEdges;
|
||||
percWidth = (bm / width) * 100;
|
||||
}
|
||||
|
||||
return {
|
||||
width: width,
|
||||
height: height,
|
||||
num_edges: numEdges,
|
||||
avg_edge_width: bm,
|
||||
avg_edge_width_perc: percWidth,
|
||||
};
|
||||
};
|
||||
|
||||
const measureBlur = (imageData) => detectBlur(reducedPixels(detectEdges(imageData)));
|
||||
|
||||
export default measureBlur;
|
||||
Loading…
x
Reference in New Issue
Block a user