Implement background blur.
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let imageSegmenter;
|
||||||
|
|
||||||
|
async function loadImageSegmenter() {
|
||||||
|
let module = await import('/third-party/tasks-vision/vision_bundle.mjs');
|
||||||
|
|
||||||
|
let vision = await module.FilesetResolver.forVisionTasks(
|
||||||
|
"/third-party/tasks-vision/wasm"
|
||||||
|
);
|
||||||
|
|
||||||
|
imageSegmenter =
|
||||||
|
await module.ImageSegmenter.createFromOptions(vision, {
|
||||||
|
baseOptions: {
|
||||||
|
modelAssetPath: '/third-party/tasks-vision/selfie_segmenter.tflite',
|
||||||
|
},
|
||||||
|
outputCategoryMask: true,
|
||||||
|
outputConfidenceMasks: false,
|
||||||
|
runningMode: 'VIDEO',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImageSegmenter();
|
||||||
|
|
||||||
|
onmessage = e => {
|
||||||
|
if(!imageSegmenter) {
|
||||||
|
// not ready yet
|
||||||
|
postMessage(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let bitmap = e.data.bitmap;
|
||||||
|
if(!(bitmap instanceof ImageBitmap))
|
||||||
|
throw new Error('Bad type for worker data');
|
||||||
|
let width = bitmap.width;
|
||||||
|
let height = bitmap.height;
|
||||||
|
imageSegmenter.segmentForVideo(
|
||||||
|
bitmap, e.data.timestamp,
|
||||||
|
result => {
|
||||||
|
/** @type{Uint8Array} */
|
||||||
|
let mask = result.categoryMask.getAsUint8Array();
|
||||||
|
let id = new ImageData(width, height);
|
||||||
|
for(let i = 0; i < mask.length; i++)
|
||||||
|
id.data[4 * i + 3] = mask[i];
|
||||||
|
createImageBitmap(id).then(ib => {
|
||||||
|
postMessage({
|
||||||
|
bitmap: bitmap,
|
||||||
|
mask: ib,
|
||||||
|
}, [bitmap, ib]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch(e) {
|
||||||
|
postMessage(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
+63
-2
@@ -1027,7 +1027,7 @@ function cancelReconsiderParameters() {
|
|||||||
* @property {string} [description]
|
* @property {string} [description]
|
||||||
* @property {(this: Filter) => Promise<void>} [init]
|
* @property {(this: Filter) => Promise<void>} [init]
|
||||||
* @property {(this: Filter) => Promise<void>} [cleanup]
|
* @property {(this: Filter) => Promise<void>} [cleanup]
|
||||||
* @property {(this: Filter, src: HTMLVideoElement, ctx: RenderingContext) => Promise<boolean>} draw
|
* @property {(this: Filter, src: HTMLVideoElement, ctx: CanvasRenderingContext2D) => Promise<boolean>} draw
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1052,7 +1052,7 @@ function Filter(stream, definition) {
|
|||||||
/** @type {HTMLCanvasElement} */
|
/** @type {HTMLCanvasElement} */
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
this.context = this.canvas.getContext('2d', {alpha: false});
|
this.context = this.canvas.getContext('2d');
|
||||||
/** @type {MediaStream} */
|
/** @type {MediaStream} */
|
||||||
this.captureStream = null;
|
this.captureStream = null;
|
||||||
/** @type {MediaStream} */
|
/** @type {MediaStream} */
|
||||||
@@ -1223,6 +1223,67 @@ let filters = {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'background-blur': {
|
||||||
|
description: 'Background blur',
|
||||||
|
init: async function(ctx) {
|
||||||
|
if(!(this instanceof Filter))
|
||||||
|
throw new Error('Bad type for this');
|
||||||
|
if(this.userdata.worker)
|
||||||
|
throw new Error("Worker already running (this shouldn't happen)")
|
||||||
|
this.userdata.worker = new Worker('/background-blur-worker.js');
|
||||||
|
},
|
||||||
|
cleanup: async function() {
|
||||||
|
if(this.userdata.worker.onmessage) {
|
||||||
|
this.userdata.worker.onmessage(null);
|
||||||
|
}
|
||||||
|
this.userdata.worker.terminate();
|
||||||
|
this.userdata.worker = null;
|
||||||
|
},
|
||||||
|
draw: async function(src, ctx) {
|
||||||
|
let bitmap = await createImageBitmap(src);
|
||||||
|
let p = new Promise((resolve, reject) => {
|
||||||
|
this.userdata.worker.onmessage = e => {
|
||||||
|
if(e && e.data) {
|
||||||
|
if(e.data instanceof Error)
|
||||||
|
reject(e.data);
|
||||||
|
else
|
||||||
|
resolve(e.data);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.userdata.worker.postMessage({
|
||||||
|
bitmap: bitmap,
|
||||||
|
timestamp: performance.now(),
|
||||||
|
}, [bitmap]);
|
||||||
|
let result = await p;
|
||||||
|
|
||||||
|
if(!result)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let mask = result.mask;
|
||||||
|
bitmap = result.bitmap;
|
||||||
|
|
||||||
|
if(ctx.canvas.width !== src.videoWidth ||
|
||||||
|
ctx.canvas.height !== src.videoHeight) {
|
||||||
|
ctx.canvas.width = src.videoWidth;
|
||||||
|
ctx.canvas.height = src.videoHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.globalCompositeOperation = 'copy';
|
||||||
|
ctx.filter = `blur(${src.videoWidth / 256}px)`;
|
||||||
|
ctx.drawImage(mask, 0, 0);
|
||||||
|
ctx.globalCompositeOperation = 'source-in';
|
||||||
|
ctx.filter = `blur(${src.videoWidth / 48}px)`;
|
||||||
|
ctx.drawImage(result.bitmap, 0, 0);
|
||||||
|
ctx.globalCompositeOperation = 'destination-atop';
|
||||||
|
ctx.filter = 'none';
|
||||||
|
ctx.drawImage(result.bitmap, 0, 0);
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function addFilters() {
|
function addFilters() {
|
||||||
|
|||||||
+10
-6
@@ -88,13 +88,17 @@ func Serve(address string, dataDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cspHeader(w http.ResponseWriter, connect string) {
|
func cspHeader(w http.ResponseWriter, connect string, unsafeEval bool) {
|
||||||
c := "connect-src ws: wss: 'self';"
|
c := "connect-src ws: wss: 'self'; "
|
||||||
if connect != "" {
|
if connect != "" {
|
||||||
c = "connect-src " + connect + " ws: wss: 'self';"
|
c = "connect-src " + connect + " ws: wss: 'self'; "
|
||||||
|
}
|
||||||
|
s := "script-src 'self'; "
|
||||||
|
if unsafeEval {
|
||||||
|
s = "script-src 'unsafe-eval' 'self'; "
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Security-Policy",
|
w.Header().Add("Content-Security-Policy",
|
||||||
c+" img-src data: 'self'; media-src blob: 'self'; default-src 'self'")
|
c+s+"img-src data: 'self'; media-src blob: 'self'; default-src 'self'")
|
||||||
|
|
||||||
// Make browser stop sending referrer information
|
// Make browser stop sending referrer information
|
||||||
w.Header().Add("Referrer-Policy", "no-referrer")
|
w.Header().Add("Referrer-Policy", "no-referrer")
|
||||||
@@ -207,7 +211,7 @@ func (fh *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cspHeader(w, "")
|
cspHeader(w, "", r.URL.Path == "/blur-background-worker.js")
|
||||||
p := r.URL.Path
|
p := r.URL.Path
|
||||||
// this ensures any leading .. are removed by path.Clean below
|
// this ensures any leading .. are removed by path.Clean below
|
||||||
if !strings.HasPrefix(p, "/") {
|
if !strings.HasPrefix(p, "/") {
|
||||||
@@ -373,7 +377,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status := g.Status(false, nil)
|
status := g.Status(false, nil)
|
||||||
cspHeader(w, status.AuthServer)
|
cspHeader(w, status.AuthServer, false)
|
||||||
serveFile(w, r, filepath.Join(StaticRoot, "galene.html"))
|
serveFile(w, r, filepath.Join(StaticRoot, "galene.html"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user