Accesses the webcam (or a fallback image if the webcam is blocked) from the webcam and applies ASCII art :)
A Pen by Lea Rosema on CodePen.
| <canvas id="canv"></canvas> | |
| <video></video> |
| const luminanceMapURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEl0lEQVRYR42XjVEiQRBGZyOADCQDyQCMADIQIwAjECNAIgAiQCMAIwAz0AwwgrVe172pZsurO6qo2Znt6f7665+ZbUopbSmlLBYLhjKfz2Pkdz6fy+VyKe/v72UymcTI73g8VpnNZhPPz8/PMd7f39f5fr8vu92uyt7c3MTzYDAI3fwaAaxWq1gYDocxshEh5oy8B8BoNIrx9fU15P4G4PPzs3x9fVXjOHB3dxd6WBdMczgcWtCDCuUfHx+xqd/vx8gaz29vbzFfLpcxypgW9AhZ/ugDJM8wpjyM8gNQMLBcLtuXl5cQnE6nFbEK2SCtgLu9vb0CuF6vr5jDKMY1PJvNKt0IakMGawigEibG43H1HOMowghKZUSWtttteMaIYjzFoD/WCQU/9PLsnNCiswJAICcXVAOAv7nAZhWhXI/Y535ziHcCUlYgrAMYFprNZhNVgKHHx8fwSM8dYYGkIQ+IHUxJJSExOWEGsBmQDAEMB/IYOQAAFEAHSKEQBSYhQlYCz8ihiPeGgLls5djn0GTD6GfO2Oz3+9bNACHhjDECzqkGGWAEiO8pq16vV3vB09NTADRpHx4eyul0qsBh6fv7O0BHFUgZnugh3hrz3A9ATtUQgiwrIzCoHgxkxixBZHU6GAApHuCJvYDYEmu8yTE3w5HHC/OHHGG/oZMRABmKnAO1DGGAjCdZMnIVmxu+N8aEBgDG/zc5DOOpCYus/cVyb6bTaYTAOucswGOTBoMIsxEFvLcv2KRo24YG4HgNg+YM1cWaVYUuQ1P7ACyY3QqaG8xRiCG8QdaeLlM5B1hjriGYIJSEKOcB9oKBXPc5aVBgDGXIHDCRatv78+BJB1AMmxPObWSsBwCSUErxkriaNADQExRkZggFHpkDJK9J6Hngfjsg1ePPTtusVqtoRFKDB5YPme0dQCC2XPsA1PITDLkAAOQYc1liVMPswdFmsVi0Zj+LGDWpZKTbmgWbzwgYYB/VwQgADHg2MNo51RchAIA3H4RQpEe2ZOs3e4SMSWa7NiFNQvZxdgCIkHmzojrYy7sIgdkt5SZO7owmZzcZbUAes1mONVs4zFDelijNL07D8XjceoORslwqUokHbMKAXVCj7DO7BZDvljDi3TAf13Ea0gk16GmYc8J35IPHrZlslzNU3icA4xUP2gHDgWRocukGA5YJozngLVdhj2RbqHdDytN+kRULquuxFxzB/hOAhh2JpbdalPEMANcFYW5gqFtFAmasANyYaWStOzfj84VFr2Tyf+aGtun3+22myed8B8hl5joASEa7KKFh7vUdRjBCZ/1tXblIwuypHhtjr0+UjF0Oed/nppT1tG2oLU3DeVcK8/ws4zUEeu69348VPc7U+1XDmtc35JRFl19aVxn/C5hmNptFK6YrQYu9njEfw91G5Glm62W/n2Ky4vEuiFxhHmYRAtsi8bRn+4HhoYMB3rMRoBrhMKLJeDznTpkB+E0JGMLpLaleSm2Vntv5HoBRO5md0c7n6B1PYBjia8tSBSDHsaG+6gPefrtfONkDAMJAvvfzXkW5LDHux6hfRYasC+AHeG3RHuX02swAAAAASUVORK5CYII='; | |
| const fontURL = 'data:image/png;base64,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'; | |
| // this is just for code highlighting in VSCode | |
| // via the glsl-literal extension | |
| const glsl = x => x; | |
| const frag = glsl` | |
| precision highp float; | |
| uniform float width; | |
| uniform float height; | |
| uniform float time; | |
| uniform sampler2D camTexture; | |
| uniform sampler2D fontTexture; | |
| uniform sampler2D lumTexture; | |
| const int useSobel = 0; | |
| const int zoom = 2; | |
| // normalize coords and correct for aspect ratio | |
| vec2 normalizeScreenCoords() | |
| { | |
| float aspectRatio = width / height; | |
| vec2 result = 2.0 * (gl_FragCoord.xy / vec2(width, height) - 0.5); | |
| result.x *= aspectRatio; | |
| return result; | |
| } | |
| float deform(vec2 p, float factor) { | |
| return sin(time * .1 + factor * p.x) * cos(time * .1 + factor * p.y); | |
| } | |
| vec4 invert(vec4 color) { | |
| return vec4(1.0 - color.rgb, 1.0); | |
| } | |
| vec4 grey(vec4 color) { | |
| // float val = (color.x + color.y + color.z) / 3.0; | |
| float val = 0.3 * color.x + 0.59 * color.y + 0.11 * color.z; | |
| return vec4(vec3(val), 1.0); | |
| } | |
| vec2 getTexCoords(vec2 position) { | |
| return 1.0 - position.xy / vec2(width, height); | |
| } | |
| float luminance(vec3 color) { | |
| return 0.2126*color.x + 0.7152*color.y + 0.0722*color.z; | |
| } | |
| // 2D Random | |
| float random (in vec2 st) { | |
| return fract(sin(dot(st.xy, | |
| vec2(12.9898,78.233))) | |
| * 43758.5453123); | |
| } | |
| vec2 camCoord(vec2 coord) { | |
| return 1.0 - coord / vec2(width, height); | |
| } | |
| vec4 sobel(in sampler2D tex, in vec2 coord) { | |
| float w = 1.0 / width; | |
| float h = 1.0 / height; | |
| vec4 n0 = texture2D(tex, coord + vec2(-w, -h)); | |
| vec4 n1 = texture2D(tex, coord + vec2( 0, -h)); | |
| vec4 n2 = texture2D(tex, coord + vec2( w, -h)); | |
| vec4 n3 = texture2D(tex, coord + vec2(-w, 0)); | |
| vec4 n4 = texture2D(tex, coord); | |
| vec4 n5 = texture2D(tex, coord + vec2( w, 0)); | |
| vec4 n6 = texture2D(tex, coord + vec2(-w, h)); | |
| vec4 n7 = texture2D(tex, coord + vec2( 0, h)); | |
| vec4 n8 = texture2D(tex, coord + vec2( w, h)); | |
| vec4 edgeH = n2 + (2.0 * n5) + n8 - (n0 + (2.0 * n3) + n6); | |
| vec4 edgeV = n0 + (2.0 * n1) + n2 - (n6 + (2.0 * n7) + n8); | |
| vec4 sobel = sqrt((edgeH * edgeH) + (edgeV * edgeV)); | |
| return sobel; | |
| } | |
| vec2 getFontCoord(int i) { | |
| float chY = floor(float(i) / 16.); | |
| float chX = mod(float(i), 16.); | |
| vec2 fontCoord = vec2((chX * 8. + mod(gl_FragCoord.x / float(zoom), 8.)) / 128., (chY * 8. + (8. - mod(gl_FragCoord.y / float(zoom), 8.))) / 128.); | |
| return fontCoord; | |
| } | |
| vec2 getLumCoord(in int i, in vec2 p) { | |
| float chY = floor(float(i) / 16.); | |
| float chX = mod(float(i), 16.); | |
| vec2 lumCoord = vec2((chX * 2. + p.x) / 32., (chY * 2. + 2. - p.y) / 32.); | |
| return lumCoord; | |
| } | |
| vec4 averageBlockColor() { | |
| vec2 a = floor(gl_FragCoord.xy / (float(zoom) * 8.)) * float(zoom) * 8.; | |
| vec2 b = a + vec2(4, 0) * float(zoom); | |
| vec2 c = b + vec2(0, 4) * float(zoom); | |
| vec2 d = c + vec2(4, 4) * float(zoom); | |
| vec4 c0 = useSobel == 1 ? grey(sobel(camTexture, camCoord(a))) : grey(texture2D(camTexture, camCoord(a))); | |
| vec4 c1 = useSobel == 1 ? grey(sobel(camTexture, camCoord(b))) : grey(texture2D(camTexture, camCoord(b))); | |
| vec4 c2 = useSobel == 1 ? grey(sobel(camTexture, camCoord(c))) : grey(texture2D(camTexture, camCoord(c))); | |
| vec4 c3 = useSobel == 1 ? grey(sobel(camTexture, camCoord(d))) : grey(texture2D(camTexture, camCoord(d))); | |
| float minDist = 9999.; | |
| int minIdx = 32; | |
| for (int i = 32; i < 127; i++) { | |
| int chY = 16 - i / 16; | |
| int chX = int(mod(float(i), 16.)); | |
| vec4 l0 = texture2D(lumTexture, getLumCoord(i, vec2(0. ,0.))); | |
| vec4 l1 = texture2D(lumTexture, getLumCoord(i, vec2(1. ,0.))); | |
| vec4 l2 = texture2D(lumTexture, getLumCoord(i, vec2(0. ,1.))); | |
| vec4 l3 = texture2D(lumTexture, getLumCoord(i, vec2(1. ,1.))); | |
| float dist = length(vec4( | |
| c0.x - l0.x, | |
| c1.x - l1.x, | |
| c2.x - l2.x, | |
| c3.x - l3.x | |
| )); | |
| if (dist < minDist) { | |
| minIdx = i; | |
| minDist = dist; | |
| } | |
| } | |
| // minIdx = 64; | |
| vec2 p = mod(gl_FragCoord.xy / 2.0, 8.); | |
| // float cY = floor(16. - float(minIdx) / 16.); | |
| // float cX = mod(float(minIdx), 16.); | |
| vec2 fontCoord = getFontCoord(minIdx); | |
| return texture2D(fontTexture, fontCoord); | |
| // return c0; | |
| } | |
| void main() { | |
| vec2 p = normalizeScreenCoords(); | |
| vec2 coord = 1.0 - gl_FragCoord.xy / vec2(width, height); | |
| gl_FragColor = averageBlockColor(); // texture2D(camTexture, coord); | |
| // display a single character from the charset (in a 16x16 table) | |
| //float chX = 2.; | |
| //float chY = 11.; | |
| //vec2 fontCoord = vec2((chX * 8. + mod(gl_FragCoord.x / 8., 8.)) / 128., (chY * 8. + (8. - mod(gl_FragCoord.y / 8., 8.))) / 128.); | |
| //gl_FragColor = texture2D(fontTexture, fontCoord); | |
| //float chX = 0.; | |
| //float chY = 1.; | |
| //vec2 lumCoord = vec2((chX * 2. + mod(gl_FragCoord.x / 8., 2.)) / 32., (chY * 2. + (2. - mod(gl_FragCoord.y / 8., 2.))) / 32.); | |
| //gl_FragColor = texture2D(lumTexture, lumCoord); | |
| } | |
| ` | |
| const vert = glsl` | |
| precision mediump float; | |
| attribute vec2 position; | |
| void main () { | |
| gl_Position = vec4(position, 0, 1.0); | |
| } | |
| ` | |
| let video = document.querySelector('video'); | |
| let fallbackImage = null; | |
| let camTexture = null; | |
| let fontTexture = null; | |
| let lumTexture = null; | |
| const glea = new GLea({ | |
| glOptions: { | |
| preserveDrawingBuffer: true | |
| }, | |
| shaders: [ | |
| GLea.fragmentShader(frag), | |
| GLea.vertexShader(vert) | |
| ], | |
| buffers: { | |
| 'position': GLea.buffer(2, [1, 1, -1, 1, 1,-1, -1,-1]) | |
| } | |
| }).create(); | |
| window.addEventListener('resize', () => { | |
| glea.resize(); | |
| }); | |
| function loop(time) { | |
| const { gl } = glea; | |
| // Upload the image into the texture. | |
| // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); | |
| if (video) { | |
| glea.setActiveTexture(0, camTexture); | |
| gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video); | |
| } | |
| glea.clear(); | |
| glea.uni('width', glea.width); | |
| glea.uni('height', glea.height); | |
| glea.uni('time', time * .005); | |
| gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
| requestAnimationFrame(loop); | |
| } | |
| function accessWebcam(video) { | |
| return new Promise((resolve, reject) => { | |
| const mediaConstraints = { audio: false, video: { width: 1280, height: 720, brightness: {ideal: 2} } }; | |
| navigator.mediaDevices.getUserMedia(mediaConstraints).then(mediaStream => { | |
| video.srcObject = mediaStream; | |
| video.onloadedmetadata = (e) => { | |
| video.play(); | |
| resolve(video); | |
| } | |
| }).catch(err => { | |
| reject(err); | |
| }); | |
| }); | |
| } | |
| function loadImage(url) { | |
| return new Promise((resolve, reject) => { | |
| const img = new Image(); | |
| img.crossOrigin = 'Anonymous'; | |
| img.src = url; | |
| img.onload = () => { | |
| resolve(img); | |
| }; | |
| img.onerror = () => { | |
| reject(img); | |
| }; | |
| }); | |
| } | |
| function takeScreenshot() { | |
| const { canvas } = glea; | |
| const anchor = document.createElement('a'); | |
| anchor.setAttribute('download', 'selfie.jpg'); | |
| anchor.setAttribute('href', canvas.toDataURL('image/jpeg', 0.92)); | |
| anchor.click(); | |
| } | |
| async function setup() { | |
| const { gl } = glea; | |
| try { | |
| await accessWebcam(video); | |
| } catch (ex) { | |
| video = null; | |
| console.error(ex.message); | |
| } | |
| // video = null; | |
| if (! video) { | |
| try { | |
| fallbackImage = await loadImage('https://placekitten.com/1280/720') | |
| } catch (ex) { | |
| console.error(ex.message); | |
| return false; | |
| } | |
| } | |
| const [fontImage, lumImage] = await Promise.all([ | |
| loadImage(fontURL), | |
| loadImage(luminanceMapURL) | |
| ]) | |
| camTexture = glea.createTexture(0); | |
| // Upload the image into the texture. | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video || fallbackImage); | |
| fontTexture = glea.createTexture(1); | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, fontImage); | |
| lumTexture = glea.createTexture(2); | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, lumImage); | |
| glea.setActiveTexture(0, camTexture); | |
| glea.uniI('camTexture', 0); | |
| glea.uniI('fontTexture', 1); | |
| glea.uniI('lumTexture', 2); | |
| loop(0); | |
| } | |
| setup(); | |
| <script src="https://unpkg.com/glea@1.1.0/dist/glea.umd.min.js"></script> |
| body { | |
| margin: 0; | |
| } | |
| canvas { | |
| display: block; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| video { | |
| display: none; | |
| } | |
| .ui { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| background: rgba(0,0,0,.6); | |
| font-family: sans-serif; | |
| color: #fff; | |
| padding: 16px; | |
| border-radius: 0 0 6px 0; | |
| } |
Accesses the webcam (or a fallback image if the webcam is blocked) from the webcam and applies ASCII art :)
A Pen by Lea Rosema on CodePen.