const model = bodySegmentation.SupportedModels.MediaPipeSelfieSegmentation;
const segmenterConfig = {
solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation@1.0.2'
const segmentationConfig = { flipHorizontal: false, landscape: true };
attribute vec3 aPosition;
attribute vec2 aTexCoord;
attribute vec4 aVertexColor;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);
gl_Position = uProjectionMatrix * viewModelPosition;
function maskShaderSource() {
uniform sampler2D content;
gl_FragColor = vec4(texture2D(content, vTexCoord).xyz, 1.) * smoothstep(0.3, 0.9, texture2D(mask, vTexCoord).x);
function warpShaderSource() {
uniform sampler2D content;
// uniform sampler2D mask;
uniform vec2 closestPoint;
float map(float value, float min1, float max1, float min2, float max2) {
min2 + (value - min1) * (max2 - min2) / (max1 - min1),
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
vec2 closestPoint = vec2(0.);
float aOff = 0.; //random(vTexCoord * 123.);
for (int i = 0; i < 250; i++) {
float a = r * ${Math.PI} * (200. + aOff * 0.1);
vec2 off = vec2(cos(a), sin(a)) * r;
vec2 pt = vTexCoord + off;
float val = texture2D(mask, pt).r;
float weight = 1.; // / (r + 10.);
closestPoint += pt * weight;
// closestR += r * weight;
// closestPoint = closestR == r ? pt : closestPoint;
closestPoint /= max(1., total);
closestR = length(closestPoint - vTexCoord);
float closestR = length(closestPoint - vTexCoord);
//gl_FragColor = vec4(vec3(map(closestR, 0., maxR, 1., 0.)), 1.);
vec2 offset = (closestPoint - vTexCoord) * 0.8 * pow(map(closestR, 0., maxR, 1., 0.), 4.);
gl_FragColor = texture2D(content, vTexCoord + offset);
font = loadFont('https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZhrib2Bg-4.ttf')
createCanvas(800, 800, WEBGL)
const isMobile = window.navigator.userAgent && /Mobi|Android/i.test(window.navigator.userAgent)
cam = createCapture(isMobile ? {
} : VIDEO, { flipped: !isMobile })
fboSize = { width: 800, height: 800 }
fg1 = createFramebuffer()
fg2 = createFramebuffer()
captureData = createGraphics(fboSize.width, fboSize.height)
maskData = createGraphics(fboSize.width, fboSize.height)
prevMaskData = createGraphics(fboSize.width, fboSize.height)
fbo = createFramebuffer(fboSize)
maskShader = createShader(...maskShaderSource())
warpShader = createShader(...warpShaderSource())
segmenter = await bodySegmentation.createSegmenter(model, segmenterConfig)
captureData.image(cam, 0, 0, fboSize.width, fboSize.height, 0, 0, cam.width, cam.height, COVER)
segmenter.segmentPeople(captureData.elt, segmentationConfig).then((res) => {
return res[0].mask.toImageData()
prevMaskData.image(maskData, 0, 0)
maskData.drawingContext.putImageData(img, 0, 0)
maskShader.setUniform('content', captureData)
maskShader.setUniform('mask', maskData)
plane(fboSize.width, fboSize.height)
bg.draw(() => image(cam, 0, 0, fboSize.width, fboSize.height, 0, 0, cam.width, cam.height, COVER))
textAlign(CENTER, CENTER)
text('Tap to stamp', 0, -4)
function mouseClicked() {
const avgPt = createVector(0, 0)
for (let i = 0; i < 1000; i++) {
const x = floor(random(maskData.width))
const y = floor(random(maskData.height))
if (maskData.pixels[(maskData.width * y + x) * 4] > 77) {
avgPt.add(x/maskData.width, y/maskData.height)
warpShader.setUniform('content', fg2)
warpShader.setUniform('closestPoint', [avgPt.x, avgPt.y])
plane(fg1.width, fg1.height)