├── .gitignore ├── .nvmrc ├── .postcssrc.json ├── .yarn └── releases │ └── yarn-3.1.0.cjs ├── .yarnrc.yml ├── LICENSE.md ├── demo ├── index.css ├── index.html ├── index.js ├── sample.mp4 ├── spread.frag.glsl └── view.frag.glsl ├── docs ├── index.3749c18b.js ├── index.3749c18b.js.map ├── index.dbe5a2ab.css ├── index.dbe5a2ab.css.map ├── index.f295124b.js ├── index.f295124b.js.map ├── index.html └── sample.6124211c.mp4 ├── index.frag.glsl ├── index.frag.js ├── index.glsl ├── index.js ├── package.json ├── readme.md ├── snap ├── demo.mp4 └── demo.png └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See "Custom Overrides" below the following generated rules. 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/node,yarn,osx 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn,osx 5 | 6 | ### Node ### 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | .pnpm-debug.log* 15 | 16 | # Diagnostic reports (https://nodejs.org/api/report.html) 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | *.lcov 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (https://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | jspm_packages/ 50 | 51 | # Snowpack dependency directory (https://snowpack.dev/) 52 | web_modules/ 53 | 54 | # TypeScript cache 55 | *.tsbuildinfo 56 | 57 | # Optional npm cache directory 58 | .npm 59 | 60 | # Optional eslint cache 61 | .eslintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variables file 79 | .env 80 | .env.test 81 | .env.production 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # Serverless directories 105 | .serverless/ 106 | 107 | # FuseBox cache 108 | .fusebox/ 109 | 110 | # DynamoDB Local files 111 | .dynamodb/ 112 | 113 | # TernJS port file 114 | .tern-port 115 | 116 | # Stores VSCode versions used for testing VSCode extensions 117 | .vscode-test 118 | 119 | # yarn v2 120 | .yarn/cache 121 | .yarn/unplugged 122 | .yarn/build-state.yml 123 | .yarn/install-state.gz 124 | .pnp.* 125 | 126 | ### Node Patch ### 127 | # Serverless Webpack directories 128 | .webpack/ 129 | 130 | ### OSX ### 131 | # General 132 | .DS_Store 133 | .AppleDouble 134 | .LSOverride 135 | 136 | # Icon must end with two \r 137 | Icon 138 | 139 | 140 | # Thumbnails 141 | ._* 142 | 143 | # Files that might appear in the root of a volume 144 | .DocumentRevisions-V100 145 | .fseventsd 146 | .Spotlight-V100 147 | .TemporaryItems 148 | .Trashes 149 | .VolumeIcon.icns 150 | .com.apple.timemachine.donotpresent 151 | 152 | # Directories potentially created on remote AFP share 153 | .AppleDB 154 | .AppleDesktop 155 | Network Trash Folder 156 | Temporary Items 157 | .apdisk 158 | 159 | ### yarn ### 160 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 161 | 162 | .yarn/* 163 | !.yarn/releases 164 | !.yarn/plugins 165 | !.yarn/sdks 166 | !.yarn/versions 167 | 168 | # if you are NOT using Zero-installs, then: 169 | # comment the following lines 170 | # !.yarn/cache 171 | 172 | # and uncomment the following lines 173 | # .pnp.* 174 | 175 | # End of https://www.toptal.com/developers/gitignore/api/node,yarn,osx 176 | 177 | # Custom Overrides 178 | 179 | /dist 180 | 181 | # Ignore files/folders marked with `.gitignore`, but not `.gitignore` itself. 182 | *.gitignore 183 | !.gitignore 184 | *.gitignore.* 185 | *.gitignore/ 186 | 187 | *.lfs 188 | *.lfs.* 189 | *.lfs/ 190 | 191 | # Re-enable Yarn zero-installs and PnP config. 192 | # !.pnp.* 193 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.13.0 2 | -------------------------------------------------------------------------------- /.postcssrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "postcss-nesting": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-3.1.0.cjs 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Eoghan O'Keeffe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/index.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { box-sizing: inherit; } 4 | 5 | html { 6 | box-sizing: border-box; 7 | background: #000; 8 | } 9 | 10 | body, 11 | .demo { 12 | display: flex; 13 | flex-flow: row wrap; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | gap: 4em; 21 | } 22 | 23 | video, 24 | canvas, 25 | img { 26 | max-width: 100%; 27 | max-height: 100%; 28 | } 29 | 30 | .webcam, 31 | .output { 32 | max-width: 100vw; 33 | max-height: 100vh; 34 | } 35 | 36 | .webcam { display: none; } 37 | 38 | .output { z-index: 1; } 39 | 40 | .demo { 41 | position: relative; 42 | cursor: copy; 43 | 44 | &.mirror { 45 | .webcam, 46 | .output { transform: scaleX(-1); } 47 | } 48 | 49 | &.overlay { 50 | cursor: alias; 51 | 52 | .webcam { display: block; } 53 | 54 | .output { 55 | position: absolute; 56 | top: 50%; 57 | left: 50%; 58 | transform: translate(-50%, -50%); 59 | } 60 | 61 | &.mirror .output { transform: translate(-50%, -50%) scaleX(-1); } 62 | } 63 | } 64 | 65 | .fallback { 66 | display: none; 67 | 68 | &[src] { 69 | display: block; 70 | position: relative; 71 | width: 100%; 72 | height: 720px; 73 | margin: auto; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 10 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | /** Any rendering library, but made with `regl` in mind. */ 2 | import getRegl from 'regl'; 3 | import getUserMedia from 'getusermedia'; 4 | import { positions, count } from '@epok.tech/gl-screen-triangle'; 5 | import range from '@epok.tech/fn-lists/range'; 6 | import map from '@epok.tech/fn-lists/map'; 7 | import each from '@epok.tech/fn-lists/each'; 8 | import { wrapIndex, wrapGet } from '@epok.tech/fn-lists/wrap-index'; 9 | 10 | import vert from '@epok.tech/gl-screen-triangle/uv-texture.vert.glsl'; 11 | import flowFrag from '../index.frag.glsl'; 12 | import spreadFrag from './spread.frag.glsl'; 13 | import viewFrag from './view.frag.glsl'; 14 | 15 | const $demo = document.querySelector('.demo'); 16 | const $video = $demo.querySelector('.webcam'); 17 | const $canvas = $demo.querySelector('.output'); 18 | const $fallback = document.querySelector('.fallback'); 19 | 20 | // Handle query parameters. 21 | const query = new URLSearchParams(location.search); 22 | 23 | const regl = getRegl({ 24 | canvas: $canvas, 25 | optionalExtensions: [ 26 | 'oes_texture_half_float', 'oes_texture_float', 'oes_texture_float_linear' 27 | ] 28 | }); 29 | 30 | const float = (regl.hasExtension('oes_texture_float_linear') && 31 | ((regl.hasExtension('oes_texture_float'))? 'float' 32 | : regl.hasExtension('oes_texture_half_float') && 'half float')); 33 | 34 | // Whether to remap values between `float`/`int` in textures. 35 | const remap = !float; 36 | 37 | const mapProps = { type: float || 'uint8', min: 'linear', mag: 'linear' }; 38 | const frameProps = { width: 0, height: 0, depth: false, stencil: false }; 39 | 40 | const getMap = () => regl.texture(mapProps); 41 | 42 | const getFrame = (color = getMap()) => 43 | regl.framebuffer({ color, ...frameProps }); 44 | 45 | // Each blur axis of spread. 46 | const spreadMaps = map(getMap, range(2), 0); 47 | const spreadFrames = map(getFrame, spreadMaps); 48 | 49 | // Past and next video frames for optical-flow. 50 | const flowFrames = map(() => getFrame(), range(2), 0); 51 | const flowTo = getFrame(); 52 | 53 | // Past and next optical-flow frames to blend. 54 | const blendFrames = map(() => getFrame(), range(2), 0); 55 | 56 | const resizers = [...spreadFrames, ...flowFrames, flowTo, ...blendFrames]; 57 | 58 | const inRange = [-1, -1, 1, 1]; 59 | const toRange = [0, 0, 1, 1]; 60 | 61 | const props = self.opticalFlow = { 62 | videoFrame: { data: $video, flipY: true }, 63 | spreadVideo: { 64 | /** 65 | * Blur the video. 66 | * 67 | * @see drawSpreadVideoProps 68 | */ 69 | frag: '#define opticalFlowSpreadBlur\n'+spreadFrag, 70 | 71 | /** 72 | * The `passes` props array spreads blur one axis after the other. 73 | * Blurs the first axis of the first frame into the next frame, then 74 | * the last axis of that frame into the first frame. 75 | * 76 | * @see drawSpreadProps 77 | */ 78 | passes: map((a, p) => ({ axis: a, pass: p }), [[1.4, 0], [0, 1.4]], 0) 79 | }, 80 | flow: { 81 | frag: ((remap)? '#define opticalFlowMap\n' : '')+flowFrag, 82 | // Pixels units; will divide `offset` by the video resolution later. 83 | offset: 3, lambda: 1e-3, speed: range(2, 1), alpha: 100, 84 | inRange, toRange, 85 | to: flowTo 86 | }, 87 | spreadFlow: { 88 | /** 89 | * Blur the past flow frames along each `axis`, shift/advect along 90 | * the `flow` by `speed`, `tint` to weaken the past flow, `blend` in 91 | * the next optical-flow frame. 92 | * 93 | * @see drawSpreadFlowProps 94 | */ 95 | frag: '#define opticalFlowSpreadBlur\n'+ 96 | '#define opticalFlowSpreadTint\n'+ 97 | '#define opticalFlowSpreadShift opticalFlowSpreadShift_flow\n'+ 98 | ((remap)? '#define opticalFlowSpreadMap\n' : '')+ 99 | '#define opticalFlowSpreadBlend\n'+ 100 | spreadFrag, 101 | 102 | other: flowTo, blend: 1, 103 | inRange, toRange, 104 | 105 | /** 106 | * Bear in mind each of the `passes` per-`axis` also apply the other 107 | * `spread` inputs; some may be better to control per-`pass`, others 108 | * across both. 109 | * 110 | * @see props.spreadVideo.passes 111 | */ 112 | passes: map((o, p) => { 113 | o.pass = p; 114 | 115 | return o; 116 | }, 117 | [ 118 | { axis: [3, 0], tint: range(4, 1), speed: range(2, 0) }, 119 | { axis: [0, 3], tint: range(4, 0.99), speed: range(2, 1) } 120 | ], 121 | 0) 122 | }, 123 | view: { 124 | frag: ((remap)? '#define opticalFlowViewMap\n' : '')+viewFrag, 125 | inRange, toRange 126 | } 127 | }; 128 | 129 | const clearView = { color: [0, 0, 0, 1], depth: 1, stencil: 0 }; 130 | const clearFlow = { ...clearView, framebuffer: flowTo }; 131 | 132 | const clearFlows = map((f) => ({ ...clearView, framebuffer: f }), flowFrames); 133 | 134 | const clearSpreads = map((f) => ({ ...clearView, framebuffer: f }), 135 | spreadFrames); 136 | 137 | const clearBlends = map((f) => ({ ...clearView, framebuffer: f }), blendFrames); 138 | 139 | const drawScreen = regl({ 140 | vert, attributes: { position: positions }, count, depth: { enable: false } 141 | }); 142 | 143 | // Spread a frame `pass`; blur along an `axis`, shift by a `flow`, `blend` 144 | // with an `other` map; if given. 145 | const drawSpread = regl({ 146 | frag: (_, p) => p.frag ?? spreadFrag, 147 | uniforms: { 148 | // Swap by `pass`; allow `frame` to override if given. 149 | frame: (_, { pass: p = 0, frame: f }) => f ?? wrapGet(p+1, spreadFrames), 150 | axis: regl.prop('axis'), 151 | tint: regl.prop('tint'), 152 | speed: regl.prop('speed'), 153 | flow: regl.prop('flow'), 154 | inRange: regl.prop('inRange'), 155 | toRange: regl.prop('toRange'), 156 | other: regl.prop('other'), 157 | blend: regl.prop('blend'), 158 | width: regl.context('drawingBufferWidth'), 159 | height: regl.context('drawingBufferHeight') 160 | }, 161 | // Swap by `pass`; allow `to` to override `framebuffer` if given. 162 | framebuffer: (_, { pass: p = 0, to }) => to ?? wrapGet(p, spreadFrames) 163 | }); 164 | 165 | // Draws the 2 spread blur `passes` across both axes one after the other. 166 | const drawSpreadProps = (props) => 167 | // Merge any common `props` into each of `props.passes`. 168 | drawSpread(map((pass) => Object.assign(pass, props, pass), props.passes, 0)); 169 | 170 | // Blur the input video's current frame, into the next flow input frame. 171 | function drawSpreadVideoProps(tick) { 172 | const { videoFrame, spreadVideo } = props; 173 | const f = wrapIndex(tick, flowFrames.length); 174 | 175 | each(regl.clear, clearSpreads); 176 | spreadMaps[1](videoFrame); 177 | 178 | regl.clear(clearFlows[f]); 179 | spreadVideo.passes[1].to = flowFrames[f]; 180 | 181 | return drawSpreadProps(spreadVideo); 182 | } 183 | 184 | // The main function of concern - optical flow of the last 2 video frames. 185 | const drawFlow = regl({ 186 | frag: (_, { frag: f = flowFrag }) => f, 187 | uniforms: { 188 | next: (c) => wrapGet(c.tick, flowFrames), 189 | past: (c) => wrapGet(c.tick+1, flowFrames), 190 | offset: regl.prop('offset'), 191 | lambda: regl.prop('lambda'), 192 | speed: regl.prop('speed'), 193 | alpha: regl.prop('alpha'), 194 | inRange: regl.prop('inRange'), 195 | toRange: regl.prop('toRange') 196 | }, 197 | framebuffer: (_, p) => p.to ?? flowTo 198 | }); 199 | 200 | function drawFlowProps() { 201 | regl.clear(clearFlow); 202 | 203 | return drawFlow(props.flow); 204 | } 205 | 206 | /** 207 | * Blur the past flow frames along each `axis`, shift/advect along the 208 | * `flow` by `speed`, `tint` to weaken the past flow, `blend` in the next 209 | * optical-flow frame. 210 | * 211 | * @see props.spreadFlow 212 | */ 213 | function drawSpreadFlowProps(tick) { 214 | const { spreadFlow } = props; 215 | const { passes } = spreadFlow; 216 | 217 | each(regl.clear, clearSpreads); 218 | spreadFlow.flow = passes[0].frame = wrapGet(tick+1, blendFrames); 219 | 220 | const b = wrapIndex(tick, blendFrames.length); 221 | 222 | regl.clear(clearBlends[b]); 223 | passes[1].to = blendFrames[b]; 224 | 225 | return drawSpreadProps(spreadFlow); 226 | } 227 | 228 | // Draw to the screen. 229 | const drawView = regl({ 230 | frag: (_, p) => p.frag ?? viewFrag, 231 | uniforms: { 232 | frame: (c) => wrapGet(c.tick, blendFrames), 233 | inRange: regl.prop('inRange'), 234 | toRange: regl.prop('toRange') 235 | }, 236 | framebuffer: (_, p) => p.to 237 | }); 238 | 239 | function drawViewProps() { 240 | regl.clear(clearView); 241 | 242 | return drawView(props.view); 243 | } 244 | 245 | // All together now. 246 | function draw({ tick: t }) { 247 | // Blur the input video's current frame. 248 | drawSpreadVideoProps(t); 249 | // Get the optical flow from the last 2 `video` frames. 250 | drawFlowProps(); 251 | // Spread the past flow frame and blend with the next one. 252 | drawSpreadFlowProps(t); 253 | // Draw to the screen. 254 | drawViewProps(); 255 | } 256 | 257 | function setup() { 258 | const { videoWidth: w, videoHeight: h } = $video; 259 | 260 | $video.width = $canvas.width = w; 261 | $video.height = $canvas.height = h; 262 | each((r) => r.resize(w, h), resizers); 263 | // Pixels units; divide `offset` by the video resolution. 264 | props.flow.offset /= Math.max(w, h, 1e3); 265 | $video.play(); 266 | 267 | // Fill the flow frames with the first frame. 268 | drawScreen(() => { 269 | drawSpreadVideoProps(0); 270 | drawSpreadVideoProps(1); 271 | }); 272 | 273 | // Start the main loop. 274 | regl.frame(() => drawScreen(draw)); 275 | 276 | $video.removeEventListener('canplay', setup); 277 | } 278 | 279 | $video.addEventListener('canplay', setup); 280 | 281 | const sampleURL = new URL('sample.mp4', import.meta.url); 282 | 283 | ((query.get('camera') === 'false')? $video.src = sampleURL 284 | : getUserMedia({ video: true }, (e, stream) => { 285 | if(e) { 286 | return console.warn(e, 'Error getting webcam stream, using sample video', 287 | $video.src = sampleURL); 288 | } 289 | 290 | (('srcObject' in $video)? $video.srcObject = stream 291 | : $video.src = URL.createObjectURL(stream)); 292 | 293 | $demo.classList.add('mirror'); 294 | })); 295 | 296 | $demo.addEventListener('click', () => { 297 | const c = $demo.classList; 298 | 299 | c[(c.contains('overlay'))? 'remove' : 'add']('overlay'); 300 | }); 301 | 302 | $fallback && (query.get('fallback') !== 'false') && 303 | ($fallback.src = $fallback.dataset.src); 304 | -------------------------------------------------------------------------------- /demo/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keeffEoghan/glsl-optical-flow/336ddaee613a8cf616af01253cd143faf1c5cb89/demo/sample.mp4 -------------------------------------------------------------------------------- /demo/spread.frag.glsl: -------------------------------------------------------------------------------- 1 | // Perform a `blur`. 2 | // #define opticalFlowSpreadBlur 3 | 4 | // Scale values output by a `tint`. 5 | // #define opticalFlowSpreadTint 6 | 7 | // Shift the sample lookup by a `flow` lookup. 8 | #define opticalFlowSpreadShift_none 0 9 | #define opticalFlowSpreadShift_frame 1 10 | #define opticalFlowSpreadShift_flow 2 11 | #ifndef opticalFlowSpreadShift 12 | #define opticalFlowSpreadShift opticalFlowSpreadShift_none 13 | #endif 14 | 15 | // Map any flow values. 16 | // #define opticalFlowSpreadMap 17 | 18 | // Blend with past frame. 19 | // #define opticalFlowSpreadBlend 20 | 21 | precision highp float; 22 | 23 | uniform sampler2D frame; 24 | 25 | // Perform a `blur`. 26 | #ifdef opticalFlowSpreadBlur 27 | uniform vec2 axis; 28 | uniform float width; 29 | uniform float height; 30 | #endif 31 | 32 | // Alter the values output, scaling by a `tint`. 33 | #ifdef opticalFlowSpreadTint 34 | uniform vec4 tint; 35 | #endif 36 | 37 | #if opticalFlowSpreadShift != opticalFlowSpreadShift_none 38 | // Shift the sample lookup by a `flow` lookup. 39 | uniform vec2 speed; 40 | 41 | // Use a separate `flow` input. 42 | #if opticalFlowSpreadShift == opticalFlowSpreadShift_flow 43 | uniform sampler2D flow; 44 | #endif 45 | 46 | // Map any flow values. 47 | #ifdef opticalFlowSpreadMap 48 | uniform vec4 inRange; 49 | uniform vec4 toRange; 50 | 51 | #pragma glslify: map = require(glsl-map) 52 | #endif 53 | #endif 54 | 55 | #ifdef opticalFlowSpreadBlend 56 | uniform sampler2D other; 57 | uniform float blend; 58 | #endif 59 | 60 | varying vec2 uv; 61 | 62 | #pragma glslify: blur = require(glsl-fast-gaussian-blur/5) 63 | 64 | #if opticalFlowSpreadShift != opticalFlowSpreadShift_none 65 | vec2 shift(vec2 uv, sampler2D flow) { 66 | vec2 f = texture2D(flow, uv).xy; 67 | 68 | // Reverse any mapping of the flow values. 69 | #ifdef opticalFlowSpreadMap 70 | f = map(f, toRange.xy, toRange.zw, inRange.xy, inRange.zw); 71 | #endif 72 | 73 | return uv+(f*speed); 74 | } 75 | #endif 76 | 77 | void main() { 78 | vec2 st = uv; 79 | 80 | // Shift the sample lookup by a `flow` lookup. 81 | #if opticalFlowSpreadShift == opticalFlowSpreadShift_flow 82 | // Use the given `flow` input. 83 | st = shift(st, flow); 84 | #elif opticalFlowSpreadShift == opticalFlowSpreadShift_frame 85 | // Use the `frame` itself as `flow` input. 86 | st = shift(st, frame); 87 | #endif 88 | 89 | // Perform a `blur`. 90 | #ifdef opticalFlowSpreadBlur 91 | vec4 c = blur(frame, st, vec2(width, height), axis); 92 | #else 93 | vec4 c = texture2D(frame, st); 94 | #endif 95 | 96 | // Map back to any given range. 97 | #ifdef opticalFlowSpreadMap 98 | c.xy = map(c.xy, toRange.xy, toRange.zw, inRange.xy, inRange.zw); 99 | #endif 100 | 101 | // Scale values output by `tint`. 102 | #ifdef opticalFlowSpreadTint 103 | c *= tint; 104 | #endif 105 | 106 | // Reverse any mapping. 107 | #ifdef opticalFlowSpreadMap 108 | c.xy = map(c.xy, inRange.xy, inRange.zw, toRange.xy, toRange.zw); 109 | #endif 110 | 111 | #ifdef opticalFlowSpreadBlend 112 | vec4 o = texture2D(other, uv); 113 | 114 | c = mix(c, o, mix(1.0-c.a, o.a, blend)); 115 | #endif 116 | 117 | gl_FragColor = c; 118 | } 119 | -------------------------------------------------------------------------------- /demo/view.frag.glsl: -------------------------------------------------------------------------------- 1 | // Map any flow values. 2 | // #define opticalFlowViewMap 3 | 4 | precision highp float; 5 | 6 | uniform sampler2D frame; 7 | 8 | // Map any flow values. 9 | #ifdef opticalFlowViewMap 10 | uniform vec4 inRange; 11 | uniform vec4 toRange; 12 | 13 | #pragma glslify: map = require(glsl-map) 14 | #endif 15 | 16 | varying vec2 uv; 17 | 18 | #pragma glslify: tau = require(glsl-constants/TWO_PI) 19 | #pragma glslify: hslToRGB = require(glsl-hsl2rgb) 20 | 21 | void main() { 22 | vec4 data = texture2D(frame, uv); 23 | float a = clamp(data.a, 0.0, 1.0); 24 | vec2 flow = data.xy; 25 | 26 | // Map any flow values. 27 | #ifdef opticalFlowViewMap 28 | flow = map(flow, toRange.xy, toRange.zw, inRange.xy, inRange.zw); 29 | #endif 30 | 31 | // Angle to hue - red right, green up, cyan left, magenta down. 32 | vec3 color = hslToRGB(atan(flow.y, flow.x)/tau, 0.9*a, 0.6*a); 33 | 34 | gl_FragColor = vec4(color, a); 35 | } 36 | -------------------------------------------------------------------------------- /docs/index.dbe5a2ab.css: -------------------------------------------------------------------------------- 1 | *,:after,:before{box-sizing:inherit}html{background:#000;box-sizing:border-box}.demo,body{align-items:center;display:flex;flex-flow:row wrap;justify-content:center}body{gap:4em;margin:0}canvas,img,video{max-height:100%;max-width:100%}.output,.webcam{max-height:100vh;max-width:100vw}.webcam{display:none}.output{z-index:1}.demo{cursor:copy;position:relative}.demo.mirror .output,.demo.mirror .webcam{transform:scaleX(-1)}.demo.overlay{cursor:alias}.demo.overlay .webcam{display:block}.demo.overlay .output{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.demo.overlay.mirror .output{transform:translate(-50%,-50%) scaleX(-1)}.fallback{display:none}.fallback[src]{display:block;height:720px;margin:auto;position:relative;width:100%} 2 | /*# sourceMappingURL=index.dbe5a2ab.css.map */ 3 | -------------------------------------------------------------------------------- /docs/index.dbe5a2ab.css.map: -------------------------------------------------------------------------------- 1 | {"mappings":"AAAA,iBAEU,kBAAqB,CAE/B,KAEE,eAAgB,CADhB,qBAEF,CAEA,WAKE,kBAAmB,CAHnB,YAAa,CACb,kBAAmB,CACnB,sBAEF,CAEA,KAEE,OAAQ,CADR,QAEF,CAEA,iBAIE,eAAgB,CADhB,cAEF,CAEA,gBAGE,gBAAiB,CADjB,eAEF,CAEA,QAAU,YAAe,CAEzB,QAAU,SAAY,CAEtB,MAEE,WAAY,CADZ,iBAsBF,CAlBI,0CACU,oBAAuB,CAGnC,cACE,YAYF,CAVE,sBAAU,aAAgB,CAE1B,sBAGE,QAAS,CAFT,iBAAkB,CAClB,OAAQ,CAER,8BACF,CAEA,6BAAmB,yCAA6C,CAIpE,UACE,YASF,CAPE,eACE,aAAc,CAGd,YAAa,CACb,WAAY,CAHZ,iBAAkB,CAClB,UAGF","sources":["demo/index.css"],"sourcesContent":["*,\n*:before,\n*:after { box-sizing: inherit; }\n\nhtml {\n box-sizing: border-box;\n background: #000;\n}\n\nbody,\n.demo {\n display: flex;\n flex-flow: row wrap;\n justify-content: center;\n align-items: center;\n}\n\nbody {\n margin: 0;\n gap: 4em;\n}\n\nvideo,\ncanvas,\nimg {\n max-width: 100%;\n max-height: 100%;\n}\n\n.webcam,\n.output {\n max-width: 100vw;\n max-height: 100vh;\n}\n\n.webcam { display: none; }\n\n.output { z-index: 1; }\n\n.demo {\n position: relative;\n cursor: copy;\n\n &.mirror {\n .webcam,\n .output { transform: scaleX(-1); }\n }\n\n &.overlay {\n cursor: alias;\n\n .webcam { display: block; }\n\n .output {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n &.mirror .output { transform: translate(-50%, -50%) scaleX(-1); }\n }\n}\n\n.fallback {\n display: none;\n\n &[src] {\n display: block;\n position: relative;\n width: 100%;\n height: 720px;\n margin: auto;\n }\n}\n"],"names":[],"version":3,"file":"index.dbe5a2ab.css.map"} -------------------------------------------------------------------------------- /docs/index.f295124b.js: -------------------------------------------------------------------------------- 1 | function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}function t(e){return e&&e.__esModule?e.default:e}var n="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},r={},a={},i=n.parcelRequireda05;null==i&&((i=function(e){if(e in r)return r[e].exports;if(e in a){var t=a[e];delete a[e];var n={id:e,exports:{}};return r[e]=n,t.call(n.exports,n,n.exports),n.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,t){a[e]=t},n.parcelRequireda05=i),i.register("g8vZD",(function(t,n){var r,a;e(t.exports,"register",(function(){return r}),(function(e){return r=e})),e(t.exports,"resolve",(function(){return a}),(function(e){return a=e}));var i={};r=function(e){for(var t=Object.keys(e),n=0;n