vdo.ninja/thirdparty/canvasFilters.js
2022-07-26 08:10:50 -04:00

254 lines
6.5 KiB
JavaScript

// 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;