+14
| language: node_js | ||
| node_js: | ||
| - 10 | ||
| install: | ||
| - npm install xvfb-maybe | ||
| - npm install | ||
| before_script: | ||
| - export DISPLAY=':99.0' | ||
| - Xvfb :99 -screen 0 1024x768x24 -extension RANDR & | ||
| script: | ||
| - xvfb-maybe npm test |
Sorry, the diff of this file is not supported yet
| /* | ||
| Name: dracula | ||
| Author: Michael Kaminsky (http://github.com/mkaminsky11) | ||
| Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) | ||
| */ | ||
| .cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters { | ||
| background-color: #282a36 !important; | ||
| color: #f8f8f2 !important; | ||
| border: none; | ||
| } | ||
| .cm-s-dracula .CodeMirror-gutters { color: #282a36; } | ||
| .cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } | ||
| .cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } | ||
| .cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } | ||
| .cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } | ||
| .cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } | ||
| .cm-s-dracula span.cm-comment { color: #6272a4; } | ||
| .cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } | ||
| .cm-s-dracula span.cm-number { color: #bd93f9; } | ||
| .cm-s-dracula span.cm-variable { color: #50fa7b; } | ||
| .cm-s-dracula span.cm-variable-2 { color: white; } | ||
| .cm-s-dracula span.cm-def { color: #50fa7b; } | ||
| .cm-s-dracula span.cm-operator { color: #ff79c6; } | ||
| .cm-s-dracula span.cm-keyword { color: #ff79c6; } | ||
| .cm-s-dracula span.cm-atom { color: #bd93f9; } | ||
| .cm-s-dracula span.cm-meta { color: #f8f8f2; } | ||
| .cm-s-dracula span.cm-tag { color: #ff79c6; } | ||
| .cm-s-dracula span.cm-attribute { color: #50fa7b; } | ||
| .cm-s-dracula span.cm-qualifier { color: #50fa7b; } | ||
| .cm-s-dracula span.cm-property { color: #66d9ef; } | ||
| .cm-s-dracula span.cm-builtin { color: #50fa7b; } | ||
| .cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; } | ||
| .cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } | ||
| .cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| const {Kampos, effects, noise} = window.kampos; | ||
| const target = document.createElement('canvas'); | ||
| target.width = 854; | ||
| target.height = 480; | ||
| const target2 = document.querySelector('#target'); | ||
| const media1 = document.querySelector('#video5'); | ||
| // uncomment here if you want to see just the turbulence | ||
| //target2.parentNode.replaceChild(target, target2); | ||
| // create the effects we need | ||
| const turbulence = effects.turbulence(noise.perlinNoise); | ||
| // create a simple effect that converts the turbulence return value into the output color | ||
| const render = {fragment: {main: 'color = vec3(turbulenceValue);'}}; | ||
| const disp = effects.displacement(); | ||
| // try playing with this factor | ||
| const AMPLITUDE = 2 * 1 / target.width; | ||
| turbulence.frequency = {x: AMPLITUDE, y: AMPLITUDE}; | ||
| turbulence.octaves = 8; | ||
| // change to false (or comment out) if you want to see the turbulence noise variant | ||
| turbulence.isFractal = true; | ||
| // init kampos | ||
| const instance = new Kampos({target, effects:[turbulence, render], noSource: true}); | ||
| const instance2 = new Kampos({target: target2, effects:[disp]}); | ||
| // start playing the noise map in a loop | ||
| const start = Date.now(); | ||
| // you can increase/decrease the time factor for a faster/slower animation | ||
| instance.play( | ||
| () => turbulence.time = (Date.now() - start) * 2 | ||
| ); | ||
| prepareVideos([media1]) | ||
| .then(() => { | ||
| const width = media1.videoWidth; | ||
| const height = media1.videoHeight; | ||
| // set media source | ||
| instance2.setSource({media: media1, width, height}); | ||
| disp.map = target; | ||
| disp.scale = {x: 0.15, y: -0.15}; | ||
| disp.textures[0].update = true; // to update | ||
| // start kampos | ||
| instance2.play(); | ||
| }); |
Sorry, the diff of this file is not supported yet
+134
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| <meta http-equiv="cache-control" content="no-cache" /> | ||
| <title>kampos | Tiny and fast effects compositor on WebGL</title> | ||
| <link href="https://fonts.googleapis.com/css?family=Nova+Round&display=swap" rel="stylesheet"> | ||
| <script async defer src="https://buttons.github.io/buttons.js"></script> | ||
| <style> | ||
| body { | ||
| min-height: 100vh; | ||
| margin: 0; | ||
| background-image: linear-gradient(-45deg, peachpuff, palegreen, plum, peachpuff, palegreen, plum), | ||
| linear-gradient(45deg, purple, darkblue, rebeccapurple, purple, darkblue, rebeccapurple), | ||
| url('./kampos.svg'); | ||
| background-blend-mode: screen; | ||
| background-repeat: no-repeat; | ||
| background-position: center; | ||
| font-family: "Nova round", cursive, fantasy; | ||
| font-size: 16px; | ||
| color: darkslateblue; | ||
| } | ||
| nav > ul { | ||
| display: flex; | ||
| width: 100vw; | ||
| margin: 0; | ||
| padding: 0; | ||
| list-style: none; | ||
| } | ||
| nav li { | ||
| flex: 1 auto; | ||
| padding: 1rem .5rem; | ||
| } | ||
| nav li.nav-items { | ||
| font-size: 16px; | ||
| font-weight: bold; | ||
| } | ||
| nav a, | ||
| nav a:visited { | ||
| color: darkslateblue; | ||
| } | ||
| nav a:hover { | ||
| color: darkslateblue; | ||
| } | ||
| main { | ||
| margin: 30vh auto 0; | ||
| text-align: center; | ||
| } | ||
| h1, | ||
| h2 { | ||
| color: #bada; | ||
| } | ||
| h1 { | ||
| font-size: 96px; | ||
| margin: 0; | ||
| text-shadow: 0 6px 10px darkslateblue; | ||
| } | ||
| h2 { | ||
| font-size: 28px; | ||
| margin: 0; | ||
| text-shadow: 0 4px 4px darkslateblue; | ||
| } | ||
| header > span { | ||
| position: absolute; | ||
| top: 50px; | ||
| right: 50px; | ||
| } | ||
| @media (min-width: 641px) { | ||
| html, body { | ||
| margin: 0; | ||
| } | ||
| main { | ||
| width: fit-content; | ||
| } | ||
| h1 { | ||
| font-size: 128px; | ||
| } | ||
| h2 { | ||
| font-size: 32px; | ||
| } | ||
| nav > ul { | ||
| width: fit-content; | ||
| margin: 0 auto; | ||
| } | ||
| nav li.nav-items { | ||
| font-size: 20px; | ||
| padding: 1rem 2rem; | ||
| } | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <header> | ||
| <nav> | ||
| <ul> | ||
| <li class="nav-items"> | ||
| <a href="./docs/">API Reference</a> | ||
| </li> | ||
| <li class="nav-items"> | ||
| <a href="./demo/">Demos</a> | ||
| </li> | ||
| <li class="nav-items"> | ||
| <a href="https://github.com/wix-incubator/kampos">Source</a> | ||
| </li> | ||
| <li> | ||
| <a class="github-button" href="https://github.com/wix-incubator/kampos" data-size="large" aria-label="Star wix-incubator/kampos on GitHub">Star</a> | ||
| </li> | ||
| </ul> | ||
| </nav> | ||
| </header> | ||
| <main> | ||
| <h1>kampos</h1> | ||
| <h2>Tiny and fast effects compositor</h2> | ||
| </main> | ||
| </body> | ||
| </html> |
+11
| <svg viewBox="0 0 313.33 710.07" xmlns="http://www.w3.org/2000/svg" width="156" height="355"> | ||
| <defs> | ||
| <linearGradient gradientTransform="rotate(90)" id="a769ca78-d298-44bf-bc15-efb581f913a2"> | ||
| <stop stop-color="#02C836" offset="0%"/> | ||
| <stop stop-color="#0055ee" offset="100%"/> | ||
| </linearGradient> | ||
| </defs> | ||
| <g> | ||
| <path fill="url(#a769ca78-d298-44bf-bc15-efb581f913a2)" d="M154.1 57.03a7.64 7.64 0 1 0-7.64-7.64 7.64 7.64 0 0 0 7.64 7.64zm3.27-10.91a3.28 3.28 0 1 1-3.27 3.27 3.27 3.27 0 0 1 3.27-3.27zm-97.06 56.71c17.76 38.44 37.87 12 159.23 80.71 16.66 9.43 27.63-11.55 13.09-18.54-44.32-21.38-81.32-66.71-39.27-114.52C137.52-33.63 40.44 6.94 33.04 33.03c33.47 0 13.27 39.55 27.27 69.8zm87.25-69.8a16.36 16.36 0 1 1-16.36 16.36 16.36 16.36 0 0 1 16.35-16.36zm2.18 299.93c-10.23 30.67-73.15 42-84-9.82-.13-.6 4.81 37.48 21.81 73.07 5.28 13.06 55.51 90.64 147.23 50.17 41.15-18.15 65-68.86 58.21-110.13-4.89-59.06-65.12-121.37-212-177.79-75-28.81-39.27-81.8-39.27-81.8-93.17 102.29-8.89 149.36 44.6 174 65.32 30 72.45 55.21 63.41 82.3zm8.69 143.73c-43.59-9.4-69.12-32.4-92.67-73.93 20.46 74.74 6.83 125.66-6.55 157C45 593.09 40.63 623.56 45.53 648c3.73 22.07 16.94 42.31 37 53.57 24.15 13.53 54.25 10.92 74.17-8.66 16.72-16.43 23.64-42.22 11.22-63.22-9.41-15.9-28.13-25.54-46.47-21.75-15.83 3.27-28.62 15.87-29.76 32.35-.79 11.36 5 23.15 14.91 28.76 13.59 7.69 32.05 2 36.87-13.31a3.92 3.92 0 0 0-7.36-2.66l-.06.15c-6.68 16.71-29.5 9.69-32.08-5.9-4.84-29.21 36.8-31.53 47.75-9.8 14 27.8-16.53 53.07-42.85 45.79-14.07-3.89-25.4-11.59-31-28.58-12.38-54.74 17.11-96 96.67-126.44 119.08-45.41 136.4-120.53 138.77-150.63-17.92 73.48-86.29 113.91-154.89 99.02z" fill-rule="evenodd"/> | ||
| </g> | ||
| </svg> |
| /** | ||
| * @function turbulence | ||
| * @param {string} noise 3D noise implementation to use | ||
| * @returns {turbulenceEffect} | ||
| * | ||
| * @example turbulence(noise) | ||
| */ | ||
| export default function (noise) { | ||
| /** | ||
| * @typedef {Object} turbulenceEffect | ||
| * @property {{x: number?, y: number?}} frequency | ||
| * @property {number} octaves | ||
| * @property {boolean} isFractal | ||
| * | ||
| * @description Generates a turbulence/fractal noise value stored into `turbulenceValue`. | ||
| * Depends on a `noise(vec3 P)` function to be declared. Currently it's possible to simply use it after {@link perlinNoiseEffect}. | ||
| * | ||
| * @example | ||
| * effect.frequency = {x: 0.0065}; | ||
| * effect.octaves = 4; | ||
| * effect.isFractal = true; | ||
| */ | ||
| return { | ||
| fragment: { | ||
| uniform: { | ||
| u_turbulenceEnabled: 'bool', | ||
| u_turbulenceFrequency: 'vec2', | ||
| u_turbulenceOctaves: 'int', | ||
| u_isFractal: 'bool', | ||
| u_time: 'float' | ||
| }, | ||
| constant: ` | ||
| ${noise} | ||
| const int MAX_OCTAVES = 9; | ||
| float turbulence (vec3 seed, vec2 frequency, int numOctaves, bool isFractal) { | ||
| float sum = 0.0; | ||
| vec3 position = vec3(0.0); | ||
| position.x = seed.x * frequency.x; | ||
| position.y = seed.y * frequency.y; | ||
| position.z = seed.z; | ||
| float ratio = 1.0; | ||
| for (int octave = 0; octave <= MAX_OCTAVES; octave++) { | ||
| if (octave > numOctaves) { | ||
| break; | ||
| } | ||
| if (isFractal) { | ||
| sum += noise(position) / ratio; | ||
| } | ||
| else { | ||
| sum += abs(noise(position)) / ratio; | ||
| } | ||
| position.x *= 2.0; | ||
| position.y *= 2.0; | ||
| ratio *= 2.0; | ||
| } | ||
| if (isFractal) { | ||
| sum = (sum + 1.0) / 2.0; | ||
| } | ||
| return clamp(sum, 0.0, 1.0); | ||
| }`, | ||
| main: ` | ||
| vec3 turbulenceSeed = vec3(gl_FragCoord.xy, u_time * 0.0001); | ||
| float turbulenceValue = turbulence(turbulenceSeed, u_turbulenceFrequency, u_turbulenceOctaves, u_isFractal);` | ||
| }, | ||
| get frequency () { | ||
| const [x, y] = this.uniforms[0].data; | ||
| return {x, y}; | ||
| }, | ||
| set frequency ({x, y}) { | ||
| if ( typeof x !== 'undefined' ) | ||
| this.uniforms[0].data[0] = x; | ||
| if ( typeof y !== 'undefined' ) | ||
| this.uniforms[0].data[1] = y; | ||
| }, | ||
| get octaves () { | ||
| return this.uniforms[1].data[0]; | ||
| }, | ||
| set octaves (value) { | ||
| this.uniforms[1].data[0] = Math.max(0, parseInt(value)); | ||
| }, | ||
| get isFractal () { | ||
| return !!this.uniforms[2].data[0]; | ||
| }, | ||
| set isFractal (toggle) { | ||
| this.uniforms[2].data[0] = +toggle; | ||
| }, | ||
| get time () { | ||
| return this.uniforms[3].data[0]; | ||
| }, | ||
| set time (value) { | ||
| this.uniforms[3].data[0] = Math.max(0, parseFloat(value)); | ||
| }, | ||
| uniforms: [ | ||
| { | ||
| name: 'u_turbulenceFrequency', | ||
| type: 'f', | ||
| data: [0.0, 0.0] | ||
| }, | ||
| { | ||
| name: 'u_turbulenceOctaves', | ||
| type: 'i', | ||
| data: [1] | ||
| }, | ||
| { | ||
| name: 'u_isFractal', | ||
| type: 'i', | ||
| data: [0] | ||
| }, | ||
| { | ||
| name: 'u_time', | ||
| type: 'f', | ||
| data: [0.0] | ||
| } | ||
| ] | ||
| }; | ||
| }; |
| /*! | ||
| * GLSL textureless classic 3D noise "cnoise", | ||
| * with an RSL-style periodic variant "pnoise". | ||
| * Author: Stefan Gustavson (stefan.gustavson@liu.se) | ||
| * Version: 2011-10-11 | ||
| * | ||
| * Many thanks to Ian McEwan of Ashima Arts for the | ||
| * ideas for permutation and gradient selection. | ||
| * | ||
| * Copyright (c) 2011 Stefan Gustavson. All rights reserved. | ||
| * Distributed under the MIT license. See LICENSE file. | ||
| * https://github.com/ashima/webgl-noise | ||
| */ | ||
| /** | ||
| * Implementation of a 3D classic Perlin noise. Exposes a `noise(vec3 P)` function for use inside fragment shaders. | ||
| */ | ||
| export default ` | ||
| vec3 mod289 (vec3 x) { | ||
| return x - floor(x * (1.0 / 289.0)) * 289.0; | ||
| } | ||
| vec4 mod289 (vec4 x) { | ||
| return x - floor(x * (1.0 / 289.0)) * 289.0; | ||
| } | ||
| vec4 permute (vec4 x) { | ||
| return mod289(((x*34.0)+1.0)*x); | ||
| } | ||
| vec4 taylorInvSqrt (vec4 r) { | ||
| return 1.79284291400159 - 0.85373472095314 * r; | ||
| } | ||
| vec3 fade (vec3 t) { | ||
| return t*t*t*(t*(t*6.0-15.0)+10.0); | ||
| } | ||
| // Classic Perlin noise | ||
| float noise (vec3 P) { | ||
| vec3 Pi0 = floor(P); // Integer part for indexing | ||
| vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 | ||
| Pi0 = mod289(Pi0); | ||
| Pi1 = mod289(Pi1); | ||
| vec3 Pf0 = fract(P); // Fractional part for interpolation | ||
| vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 | ||
| vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); | ||
| vec4 iy = vec4(Pi0.yy, Pi1.yy); | ||
| vec4 iz0 = Pi0.zzzz; | ||
| vec4 iz1 = Pi1.zzzz; | ||
| vec4 ixy = permute(permute(ix) + iy); | ||
| vec4 ixy0 = permute(ixy + iz0); | ||
| vec4 ixy1 = permute(ixy + iz1); | ||
| vec4 gx0 = ixy0 * (1.0 / 7.0); | ||
| vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; | ||
| gx0 = fract(gx0); | ||
| vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); | ||
| vec4 sz0 = step(gz0, vec4(0.0)); | ||
| gx0 -= sz0 * (step(0.0, gx0) - 0.5); | ||
| gy0 -= sz0 * (step(0.0, gy0) - 0.5); | ||
| vec4 gx1 = ixy1 * (1.0 / 7.0); | ||
| vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; | ||
| gx1 = fract(gx1); | ||
| vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); | ||
| vec4 sz1 = step(gz1, vec4(0.0)); | ||
| gx1 -= sz1 * (step(0.0, gx1) - 0.5); | ||
| gy1 -= sz1 * (step(0.0, gy1) - 0.5); | ||
| vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); | ||
| vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); | ||
| vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); | ||
| vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); | ||
| vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); | ||
| vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); | ||
| vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); | ||
| vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); | ||
| vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); | ||
| g000 *= norm0.x; | ||
| g010 *= norm0.y; | ||
| g100 *= norm0.z; | ||
| g110 *= norm0.w; | ||
| vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); | ||
| g001 *= norm1.x; | ||
| g011 *= norm1.y; | ||
| g101 *= norm1.z; | ||
| g111 *= norm1.w; | ||
| float n000 = dot(g000, Pf0); | ||
| float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); | ||
| float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); | ||
| float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); | ||
| float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); | ||
| float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); | ||
| float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); | ||
| float n111 = dot(g111, Pf1); | ||
| vec3 fade_xyz = fade(Pf0); | ||
| vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); | ||
| vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); | ||
| float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); | ||
| return 2.2 * n_xyz; | ||
| }`; |
+36
-3
@@ -1,3 +0,36 @@ | ||
| import './utils'; | ||
| import './disp'; | ||
| import './hue-fade'; | ||
| function updatePreview (iframe, example, videos) { | ||
| iframe.setAttribute('srcdoc', getIFrameHTML({example, videos})) | ||
| } | ||
| function getIFrameHTML ({example, videos}) { | ||
| return `<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <style> | ||
| body, html {margin: 0; height: 100%; overflow: hidden;} | ||
| canvas {width: 100%; height: 100%;} | ||
| video {display: none;} | ||
| </style> | ||
| <script src="../index.js"></script> | ||
| <script src="./utils.js"></script> | ||
| </head> | ||
| <body> | ||
| ${videos} | ||
| <canvas id="target"></canvas> | ||
| <script> | ||
| ${example} | ||
| </script> | ||
| </body> | ||
| </html>`; | ||
| } | ||
| window.updatePreview = updatePreview; | ||
| const navOffHandler = () => document.body.classList.remove('nav-open'); | ||
| document.querySelector('#nav-on') | ||
| .addEventListener('click', () => { | ||
| document.body.classList.add('nav-open'); | ||
| setTimeout(() => document.body.addEventListener('click', navOffHandler, {once: true}), 0); | ||
| }); |
+11
-4
@@ -41,2 +41,3 @@ const {Kampos, transitions} = window.kampos; | ||
| // try playing with the x/y and +/- for different transition effects | ||
| this.transition.sourceScale = {x: this.dispScale}; | ||
@@ -85,2 +86,3 @@ this.transition.toScale = {x: -this.dispScale}; | ||
| // dividing by 500 is just enough to slow down the effect | ||
| // you can change the sin() function with a different one for a different easing | ||
| let p = Math.abs(Math.sin(now / 500)); | ||
@@ -112,3 +114,3 @@ p = this.direction ? p : 1 - p; | ||
| const video2 = document.querySelector('#video2'); | ||
| const target1 = document.querySelector('#target1'); | ||
| const target = document.querySelector('#target'); | ||
@@ -118,4 +120,9 @@ const trans = new Transition({ | ||
| vid2: video2, | ||
| target: target1, | ||
| target, | ||
| // switch between the different displacement-map images and refresh to see different effects | ||
| //disp: 'disp-cloud.png', | ||
| //disp: 'disp-liquid.jpg', | ||
| //disp: 'disp-tri.jpg', | ||
| disp: 'disp-snow.jpg', | ||
| // change this value and refresh to see how it affects the transition | ||
| dispScale: 1.0 | ||
@@ -127,3 +134,3 @@ }); | ||
| */ | ||
| target1.addEventListener('mouseenter', () => trans.forward()); | ||
| target1.addEventListener('mouseleave', () => trans.backward()); | ||
| target.addEventListener('mouseenter', () => trans.forward()); | ||
| target.addEventListener('mouseleave', () => trans.backward()); |
+1
-1
@@ -5,3 +5,3 @@ const {Kampos, effects, transitions} = window.kampos; | ||
| const media2 = document.querySelector('#video4'); | ||
| const target = document.querySelector('#target2'); | ||
| const target = document.querySelector('#target'); | ||
@@ -8,0 +8,0 @@ // create the effects/transitions we need |
+250
-120
@@ -7,5 +7,6 @@ <!DOCTYPE html> | ||
| <meta http-equiv="cache-control" content="no-cache" /> | ||
| <meta http-equiv="pragma" content="no-cache" /> | ||
| <title>Kampos Displacement Demo</title> | ||
| <title>kampos Demos</title> | ||
| <link href="https://fonts.googleapis.com/css?family=Nova+Round&display=swap" rel="stylesheet"> | ||
| <link rel="stylesheet" href="./lib/codemirror/codemirror.css"> | ||
| <link rel="stylesheet" href="lib/codemirror/dracula.css"> | ||
| <style> | ||
@@ -18,25 +19,28 @@ body { | ||
| font-family: "Nova round", cursive, fantasy; | ||
| color: #bada; | ||
| } | ||
| header { | ||
| width: fit-content; | ||
| margin: 40vh auto 0; | ||
| text-align: center; | ||
| text-align: right; | ||
| } | ||
| h1 { | ||
| font-size: 96px; | ||
| margin: 0; | ||
| text-shadow: 0 6px 10px darkslateblue; | ||
| nav { | ||
| position: fixed; | ||
| width: 256px; | ||
| min-height: 100vh; | ||
| z-index: 10; | ||
| top: 0; | ||
| background-color: #DDA0DDee; | ||
| transform: translateX(-100%); | ||
| transition: transform .5s ease; | ||
| } | ||
| h2 { | ||
| font-size: 28px; | ||
| margin: 0; | ||
| text-shadow: 0 4px 4px darkslateblue; | ||
| .nav-open nav { | ||
| transform: translateX(0); | ||
| } | ||
| video, | ||
| canvas { | ||
| main { | ||
| width: 100vw; | ||
| } | ||
| iframe { | ||
| display: block; | ||
@@ -46,18 +50,64 @@ vertical-align: top; | ||
| canvas { | ||
| iframe { | ||
| width: 100vw; | ||
| height: auto; | ||
| height: calc(480 / 854 * 100vw); | ||
| border: none; | ||
| outline: none; | ||
| } | ||
| video { | ||
| display: none; | ||
| section { | ||
| position: relative; | ||
| margin: 0 auto 10vh; | ||
| } | ||
| header > span { | ||
| nav ul { | ||
| margin: 1rem 0; | ||
| padding: 0; | ||
| list-style: none; | ||
| } | ||
| nav li { | ||
| padding: 1rem; | ||
| } | ||
| p { | ||
| padding: 1rem; | ||
| } | ||
| button { | ||
| cursor: pointer; | ||
| background: linear-gradient(45deg, beige, plum); | ||
| } | ||
| .code-container { | ||
| position: relative; | ||
| } | ||
| .refresh-button { | ||
| position: absolute; | ||
| top: 50px; | ||
| right: 50px; | ||
| top: 1px; | ||
| right: 1px; | ||
| z-index: 10; | ||
| padding: 1em 2em; | ||
| font-size: 16px; | ||
| font-weight: bold; | ||
| } | ||
| @media (min-width: 854px) { | ||
| header button { | ||
| cursor: pointer; | ||
| } | ||
| #nav-off { | ||
| display: none; | ||
| } | ||
| .nav-open #nav-off { | ||
| display: inline; | ||
| } | ||
| .nav-open #nav-on { | ||
| display: none; | ||
| } | ||
| @media (min-width: 1110px) { | ||
| html, body { | ||
@@ -67,2 +117,24 @@ margin: 0; | ||
| body { | ||
| display: grid; | ||
| grid-template: "nav main" 1fr / 256px calc(100vw - 256px); | ||
| } | ||
| header { | ||
| display: none; | ||
| } | ||
| nav { | ||
| grid-area: nav; | ||
| position: fixed; | ||
| min-height: 100vh; | ||
| background-color: transparent; | ||
| transform: none; | ||
| } | ||
| main { | ||
| grid-area: main; | ||
| width: auto; | ||
| } | ||
| h1 { | ||
@@ -80,6 +152,5 @@ font-size: 128px; | ||
| width: 854px; | ||
| margin: 10vh auto; | ||
| } | ||
| canvas { | ||
| iframe { | ||
| flex: auto; | ||
@@ -95,3 +166,3 @@ width: 854px; | ||
| #code1 + .CodeMirror { | ||
| height: 118rem; | ||
| height: 125rem; | ||
| } | ||
@@ -102,100 +173,117 @@ | ||
| } | ||
| #code3 + .CodeMirror { | ||
| height: 54rem; | ||
| } | ||
| } | ||
| </style> | ||
| <link href="https://fonts.googleapis.com/css?family=Nova+Round&display=swap" rel="stylesheet"> | ||
| <script async defer src="https://buttons.github.io/buttons.js"></script> | ||
| </head> | ||
| <body> | ||
| <header> | ||
| <a class="github-button" href="https://github.com/wix-incubator/kampos" data-size="large" aria-label="Star wix-incubator/kampos on GitHub">Star</a> | ||
| <h1>kampos</h1> | ||
| <h2>Tiny and fast effects compositor</h2> | ||
| </header> | ||
| <!--<video id="video"--> | ||
| <!--src="https://video.wixstatic.com/video/11062b_6ceb80744f45401caf9eb666caeb9887/480p/mp4/file.mp4"--> | ||
| <!--autoplay--> | ||
| <!--loop--> | ||
| <!--muted--> | ||
| <!--preload="auto"--> | ||
| <!--crossorigin="anonymous"--> | ||
| <!--playsinline></video>--> | ||
| <template id="videos1"> | ||
| <video id="video1" | ||
| src="./cloudy-night.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| <!--<video id="video2"--> | ||
| <!--src="https://video.wixstatic.com/video/11062b_1bb2d040b6da423fa2461982bd68bb12/480p/mp4/file.mp4"--> | ||
| <!--autoplay--> | ||
| <!--loop--> | ||
| <!--muted--> | ||
| <!--preload="auto"--> | ||
| <!--crossorigin="anonymous"--> | ||
| <!--playsinline></video>--> | ||
| <video id="video2" | ||
| src="./starry-night.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| </template> | ||
| <video id="video1" | ||
| src="https://video.wixstatic.com/video/11062b_2ccb88c1c6de4151b51879d6c90fca9d/480p/mp4/file.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| <template id="videos2"> | ||
| <video id="video3" | ||
| src="./wheat-field.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| <video id="video2" | ||
| src="https://video.wixstatic.com/video/11062b_735a34726eb64df29a56b30b60fe2db8/480p/mp4/file.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| <video id="video4" | ||
| src="./shell-beach.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| </template> | ||
| <video id="video3" | ||
| src="https://video.wixstatic.com/video/11062b_3da4a26484194105bde5b3935f5afb7b/480p/mp4/file.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| <template id="videos3"> | ||
| <video id="video5" | ||
| src="./man-on-beach.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| </template> | ||
| <nav> | ||
| <ul> | ||
| <li> | ||
| <a href="#displacement-transition">Displacement transition</a> | ||
| </li> | ||
| <li> | ||
| <a href="#fade-hue">Cross-fade with hue-rotation</a> | ||
| </li> | ||
| <li> | ||
| <a href="#turbulence">Fractal noise displacement</a> | ||
| </li> | ||
| </ul> | ||
| </nav> | ||
| <header> | ||
| <button id="nav-on"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="arcs"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg> | ||
| </button> | ||
| <button id="nav-off"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="arcs"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> | ||
| </button> | ||
| </header> | ||
| <main> | ||
| <section id="displacement-transition"> | ||
| <p> | ||
| Transition between 2 videos with a displacement effect.<br/> | ||
| Hover the video to see it in action.<br/> | ||
| * On mobile tap inside/outside the video. | ||
| </p> | ||
| <iframe id="preview1" srcdoc="" sandbox="allow-scripts allow-same-origin"></iframe> | ||
| <div class="code-container"> | ||
| <button class="refresh-button" id="refresh1">Refresh</button> | ||
| <textarea id="code1"></textarea> | ||
| </div> | ||
| </section> | ||
| <section id="fade-hue"> | ||
| <p> | ||
| Transition between 2 videos with a cross-fade effect & apply a hue-rotate filter.<br/> | ||
| Move the mouse (tap on mobile) around over the video to see it in action. | ||
| </p> | ||
| <iframe id="preview2" srcdoc="" sandbox="allow-scripts allow-same-origin"></iframe> | ||
| <div class="code-container"> | ||
| <button class="refresh-button" id="refresh2">Refresh</button> | ||
| <textarea id="code2"></textarea> | ||
| </div> | ||
| </section> | ||
| <section id="turbulence"> | ||
| <p> | ||
| Paint turbulence noise, animate it, and use it as a displacement map for the video. | ||
| </p> | ||
| <iframe id="preview3" srcdoc="" sandbox="allow-scripts allow-same-origin"></iframe> | ||
| <div class="code-container"> | ||
| <button class="refresh-button" id="refresh3">Refresh</button> | ||
| <textarea id="code3"></textarea> | ||
| </div> | ||
| </section> | ||
| </main> | ||
| <video id="video4" | ||
| src="https://video.wixstatic.com/video/11062b_62f64d80001a4490b6cc785d64359c6c/480p/mp4/file.mp4" | ||
| autoplay | ||
| loop | ||
| muted | ||
| preload="auto" | ||
| crossorigin="anonymous" | ||
| playsinline></video> | ||
| <!--<video id="video7"--> | ||
| <!--src="https://video.wixstatic.com/video/11062b_830709c744404d6ea7a10dcfd88e3a48/480p/mp4/file.mp4"--> | ||
| <!--autoplay--> | ||
| <!--loop--> | ||
| <!--muted--> | ||
| <!--preload="auto"--> | ||
| <!--crossorigin="anonymous"--> | ||
| <!--playsinline></video>--> | ||
| <!--<video id="video8"--> | ||
| <!--src="https://video.wixstatic.com/video/11062b_ba2c921477734f8b84ef0c71f64a2562/480p/mp4/file.mp4"--> | ||
| <!--autoplay--> | ||
| <!--loop--> | ||
| <!--muted--> | ||
| <!--preload="auto"--> | ||
| <!--crossorigin="anonymous"--> | ||
| <!--playsinline></video>--> | ||
| <!--<canvas id="target"></canvas>--> | ||
| <!--<canvas id="target2"></canvas>--> | ||
| <!--<canvas id="target3"></canvas>--> | ||
| <section> | ||
| <h2>Displacement transition:</h2> | ||
| <canvas id="target1"></canvas> | ||
| <textarea id="code1"></textarea> | ||
| </section> | ||
| <section> | ||
| <h2>Cross-fade transition with hue-rotation:</h2> | ||
| <canvas id="target2"></canvas> | ||
| <textarea id="code2"></textarea> | ||
| </section> | ||
| <script src="../index.js"></script> | ||
| <script src="./lib/codemirror/codemirror.js"></script> | ||
@@ -205,2 +293,5 @@ <script src="./lib/codemirror/javascript.js"></script> | ||
| const text1 = document.querySelector('#code1'); | ||
| const preview1 = document.querySelector('#preview1'); | ||
| const videos1 = document.querySelector('#videos1'); | ||
| const refresh1 = document.querySelector('#refresh1'); | ||
@@ -211,9 +302,19 @@ fetch('./disp.js') | ||
| text1.value = text; | ||
| CodeMirror.fromTextArea(text1, { | ||
| const doc = CodeMirror.fromTextArea(text1, { | ||
| value: text, | ||
| lineNumbers: true | ||
| lineNumbers: true, | ||
| theme: 'dracula' | ||
| }); | ||
| const update = () => updatePreview(preview1, doc.getValue(), videos1.innerHTML); | ||
| refresh1.addEventListener('click', update); | ||
| update(); | ||
| }); | ||
| const text2 = document.querySelector('#code2'); | ||
| const preview2 = document.querySelector('#preview2'); | ||
| const videos2 = document.querySelector('#videos2'); | ||
| const refresh2 = document.querySelector('#refresh2'); | ||
@@ -224,7 +325,36 @@ fetch('./hue-fade.js') | ||
| text2.value = text; | ||
| CodeMirror.fromTextArea(text2, { | ||
| const doc = CodeMirror.fromTextArea(text2, { | ||
| value: text, | ||
| lineNumbers: true | ||
| lineNumbers: true, | ||
| theme: 'dracula' | ||
| }); | ||
| const update = () => updatePreview(preview2, doc.getValue(), videos2.innerHTML); | ||
| refresh2.addEventListener('click', update); | ||
| update(); | ||
| }); | ||
| const text3 = document.querySelector('#code3'); | ||
| const preview3 = document.querySelector('#preview3'); | ||
| const videos3 = document.querySelector('#videos3'); | ||
| const refresh3 = document.querySelector('#refresh3'); | ||
| fetch('./turbulence.js') | ||
| .then(resp => resp.text()) | ||
| .then(text => { | ||
| text3.value = text; | ||
| const doc = CodeMirror.fromTextArea(text3, { | ||
| value: text, | ||
| lineNumbers: true, | ||
| theme: 'dracula' | ||
| }); | ||
| const update = () => updatePreview(preview3, doc.getValue(), videos3.innerHTML); | ||
| refresh3.addEventListener('click', update); | ||
| update(); | ||
| }); | ||
| </script> | ||
@@ -231,0 +361,0 @@ <script src="index.js"></script> |
+28
-246
| (function () { | ||
| 'use strict'; | ||
| /* | ||
| * Most simple image loader | ||
| * You'll probably have something like this already | ||
| */ | ||
| function loadImage$1 (src) { | ||
| return new Promise(resolve => { | ||
| const img = new Image(); | ||
| img.onload = function () { | ||
| resolve(this); | ||
| }; | ||
| img.src = src; | ||
| }); | ||
| function updatePreview (iframe, example, videos) { | ||
| iframe.setAttribute('srcdoc', getIFrameHTML({example, videos})); | ||
| } | ||
| window.loadImage = loadImage$1; | ||
| /* | ||
| * Minimal, cross-browser logic for playing videos and making sure | ||
| * they are ready to work with | ||
| */ | ||
| function prepareVideos$1 (videos) { | ||
| return new Promise(resolve => { | ||
| let playing = 0; | ||
| let timeupdate = 0; | ||
| function canPlay (e) { | ||
| e.target.play(); | ||
| } | ||
| const isPlaying = e => { | ||
| playing += 1; | ||
| e.target.removeEventListener('playing', isPlaying, true); | ||
| check(); | ||
| }; | ||
| const isTimeupdate = (e) => { | ||
| timeupdate += 1; | ||
| e.target.removeEventListener('timeupdate', isTimeupdate, true); | ||
| check(); | ||
| }; | ||
| const check = () => { | ||
| if (playing === videos.length && timeupdate === videos.length) { | ||
| videos.forEach(vid => { | ||
| vid.removeEventListener('canplay', canPlay, true); | ||
| }); | ||
| resolve(); | ||
| } | ||
| }; | ||
| videos.forEach(vid => { | ||
| vid.addEventListener('playing', isPlaying, true); | ||
| vid.addEventListener('timeupdate', isTimeupdate, true); | ||
| vid.addEventListener('canplay', canPlay, true); | ||
| }); | ||
| }); | ||
| function getIFrameHTML ({example, videos}) { | ||
| return `<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <style> | ||
| body, html {margin: 0; height: 100%; overflow: hidden;} | ||
| canvas {width: 100%; height: 100%;} | ||
| video {display: none;} | ||
| </style> | ||
| <script src="../index.js"></script> | ||
| <script src="./utils.js"></script> | ||
| </head> | ||
| <body> | ||
| ${videos} | ||
| <canvas id="target"></canvas> | ||
| <script> | ||
| ${example} | ||
| </script> | ||
| </body> | ||
| </html>`; | ||
| } | ||
| window.prepareVideos = prepareVideos$1; | ||
| window.updatePreview = updatePreview; | ||
| const {Kampos, transitions} = window.kampos; | ||
| const transitionDisplacement = transitions.displacement; | ||
| const navOffHandler = () => document.body.classList.remove('nav-open'); | ||
| /* | ||
| * Wrapper class for transition logic. | ||
| * This is a simple vanilla implementation | ||
| */ | ||
| class Transition { | ||
| constructor ({vid1, vid2, target, disp, dispScale}) { | ||
| /* | ||
| * prepare here everything we need | ||
| */ | ||
| this.vid1 = vid1; | ||
| this.vid2 = vid2; | ||
| this.target = target; | ||
| this.dispScale = dispScale; | ||
| this.transition = transitionDisplacement(); | ||
| document.querySelector('#nav-on') | ||
| .addEventListener('click', () => { | ||
| document.body.classList.add('nav-open'); | ||
| this.direction = 1; | ||
| this.startTime = 0; | ||
| // init kampos | ||
| this.kampos = new Kampos({target, effects: [this.transition]}); | ||
| // load the displacement map image | ||
| const dispReady = loadImage(disp); | ||
| // make sure videos are loaded and playing | ||
| prepareVideos([this.vid1, this.vid2]) | ||
| .then(() => { | ||
| const width = this.vid1.videoWidth; | ||
| const height = this.vid1.videoHeight; | ||
| dispReady.then(img => { | ||
| /* | ||
| * set transition values | ||
| */ | ||
| this.transition.map = img; | ||
| this.transition.to = this.vid2; | ||
| this.transition.sourceScale = {x: this.dispScale}; | ||
| this.transition.toScale = {x: -this.dispScale}; | ||
| // set media source | ||
| this.kampos.setSource({media: this.vid1, width, height}); | ||
| // start kampos | ||
| this.kampos.play(); | ||
| }); | ||
| }); | ||
| } | ||
| /* | ||
| * start animation playback forward | ||
| */ | ||
| forward () { | ||
| this.direction = 1; | ||
| this.startTime = Date.now(); | ||
| this.loop(); | ||
| } | ||
| /* | ||
| * start animation playback backwards | ||
| */ | ||
| backward () { | ||
| this.direction = 0; | ||
| this.startTime = Date.now(); | ||
| this.loop(); | ||
| } | ||
| /* | ||
| * This will probably be a callback you'll provide to your animation library | ||
| */ | ||
| tick (p) { | ||
| this.transition.progress = p; | ||
| } | ||
| /* | ||
| * This will usually be implemented by an animation library you may already have in your project | ||
| */ | ||
| loop () { | ||
| const now = Date.now() - this.startTime; | ||
| // dividing by 500 is just enough to slow down the effect | ||
| let p = Math.abs(Math.sin(now / 500)); | ||
| p = this.direction ? p : 1 - p; | ||
| this.tick(p); | ||
| let nextTick = () => this.loop(); | ||
| // we choose a cutoff value where the progress value | ||
| // is almost 0/1, depending on direction | ||
| // and then stop the animation by just rendering | ||
| // 1 extra tick with the final value (0 or 1 respectively). | ||
| if (this.direction) { | ||
| if (p * 100 >= 99) { | ||
| nextTick = () => this.tick(1); | ||
| } | ||
| } | ||
| else if (p * 100 <= 1) { | ||
| nextTick = () => this.tick(0); | ||
| } | ||
| window.requestAnimationFrame(nextTick); | ||
| } | ||
| } | ||
| const video1 = document.querySelector('#video1'); | ||
| const video2 = document.querySelector('#video2'); | ||
| const target1 = document.querySelector('#target1'); | ||
| const trans = new Transition({ | ||
| vid1: video1, | ||
| vid2: video2, | ||
| target: target1, | ||
| disp: 'disp-snow.jpg', | ||
| dispScale: 1.0 | ||
| }); | ||
| /* | ||
| * register event handlers for interaction | ||
| */ | ||
| target1.addEventListener('mouseenter', () => trans.forward()); | ||
| target1.addEventListener('mouseleave', () => trans.backward()); | ||
| const {Kampos: Kampos$1, effects, transitions: transitions$1} = window.kampos; | ||
| const media1 = document.querySelector('#video3'); | ||
| const media2 = document.querySelector('#video4'); | ||
| const target = document.querySelector('#target2'); | ||
| // create the effects/transitions we need | ||
| const hueSat = effects.hueSaturation(); | ||
| const fade = transitions$1.fade(); | ||
| // init kampos | ||
| const instance = new Kampos$1({target, effects:[fade, hueSat]}); | ||
| // make sure videos are loaded and playing | ||
| prepareVideos([media1, media2]) | ||
| .then(() => { | ||
| const width = media1.videoWidth; | ||
| const height = media1.videoHeight; | ||
| // set media source | ||
| instance.setSource({media: media1, width, height}); | ||
| // set media to transition into | ||
| fade.to = media2; | ||
| // start kampos | ||
| instance.play(); | ||
| setTimeout(() => document.body.addEventListener('click', navOffHandler, {once: true}), 0); | ||
| }); | ||
| let x, y, rect; | ||
| let drawing = false; | ||
| // this is invoked once in every animation frame, while there's a mouse move over the canvas | ||
| function tick () { | ||
| fade.progress = Math.max(0, Math.min(1, (y - rect.y) / rect.height)); | ||
| hueSat.hue = Math.max(0, Math.min(1, (x - rect.x) / rect.width)) * 360 - 180; | ||
| drawing = false; | ||
| } | ||
| // handler for detecting mouse move | ||
| const moveHandler = e => { | ||
| const {clientX, clientY} = e; | ||
| // cache mouse location | ||
| x = clientX; | ||
| y = clientY; | ||
| // only once! a frame | ||
| if (!drawing) { | ||
| drawing = true; | ||
| // read here | ||
| rect = target.getBoundingClientRect(); | ||
| // write on next frame | ||
| requestAnimationFrame(tick); | ||
| } | ||
| }; | ||
| /* | ||
| * register event handlers for interaction | ||
| */ | ||
| target.addEventListener('mouseenter', () => { | ||
| target.addEventListener('mousemove', moveHandler); | ||
| }); | ||
| target.addEventListener('mouseleave', () => { | ||
| target.removeEventListener('mousemove', moveHandler); | ||
| }); | ||
| }()); |
@@ -18,2 +18,3 @@ toc: | ||
| - displacement | ||
| - turbulence | ||
| - brightnessContrastEffect | ||
@@ -24,2 +25,3 @@ - hueSaturationEffect | ||
| - displacementEffect | ||
| - turbulenceEffect | ||
| - name: transitions | ||
@@ -26,0 +28,0 @@ children: |
+1
-1
| { | ||
| "name": "kampos", | ||
| "version": "0.2.2", | ||
| "version": "0.3.0", | ||
| "description": "Tiny and fast effects compositor on WebGL", | ||
@@ -5,0 +5,0 @@ "registry": "https://registry.npmjs.org/", |
+16
-12
@@ -1,2 +0,6 @@ | ||
| # kampos | ||
| <p align="center"> | ||
| <img width="100" src="./kampos.svg?sanitize=true"> | ||
| </p> | ||
| # kampos [](https://travis-ci.com/wix-incubator/kampos) | ||
| ### Tiny and fast effects compositor on WebGL | ||
@@ -6,9 +10,9 @@ | ||
| Just like [SVG filter effects](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Filter_effects), | ||
| only using WebGL, and hence works everywhere! | ||
| only using WebGL, which means it works everywhere! | ||
| ## Demo | ||
| Watch a [live demo](https://wix-incubator.github.io/kampos/demo/). | ||
| Check out the [live demo](https://wix-incubator.github.io/kampos/demo/). | ||
| ## Documentation | ||
| For API reference and examples read [the docs](https://wix-incubator.github.io/kampos/docs/). | ||
| For API reference and examples, read [the docs](https://wix-incubator.github.io/kampos/docs/). | ||
@@ -24,3 +28,3 @@ ## Features | ||
| Here's a simple example for using kampos: | ||
| ``` | ||
| ```javascript | ||
| import {Kampos, effects} from 'kampos'; | ||
@@ -44,3 +48,3 @@ | ||
| ### npm example: | ||
| ``` | ||
| ```bash | ||
| npm install kampos | ||
@@ -50,9 +54,9 @@ ``` | ||
| Import the default build: | ||
| ``` | ||
| ```javascript | ||
| import {Kampos, Ticker, effects, transitions} from 'kampos'; | ||
| ``` | ||
| Or just what you need: | ||
| ``` | ||
| import {Kampos} from './node_modules/kampos/src/kampos'; | ||
| Or take just what you need: | ||
| ```javascript | ||
| import Kampos from './node_modules/kampos/src/kampos'; | ||
| import duotone from './node_modules/kampos/src/effects/duotone'; | ||
@@ -63,3 +67,3 @@ import displacement from './node_modules/kampos/src/effects/displacement'; | ||
| ## Building locally | ||
| ``` | ||
| ```bash | ||
| npm install | ||
@@ -70,3 +74,3 @@ npm run build | ||
| ## Running tests | ||
| ``` | ||
| ```bash | ||
| npm run test | ||
@@ -73,0 +77,0 @@ ``` |
+118
-59
@@ -10,3 +10,3 @@ export default { | ||
| const vertexTemplate = ({ | ||
| const vertexSimpleTemplate = ({ | ||
| uniform = '', | ||
@@ -21,2 +21,22 @@ attribute = '', | ||
| ${attribute} | ||
| attribute vec2 a_position; | ||
| ${varying} | ||
| const vec3 lumcoeff = vec3(0.2125, 0.7154, 0.0721); | ||
| ${constant} | ||
| void main() { | ||
| ${main} | ||
| gl_Position = vec4(a_position.xy, 0.0, 1.0); | ||
| }`; | ||
| const vertexMediaTemplate = ({ | ||
| uniform = '', | ||
| attribute = '', | ||
| varying = '', | ||
| constant = '', | ||
| main = '' | ||
| }) => ` | ||
| precision mediump float; | ||
| ${uniform} | ||
| ${attribute} | ||
| attribute vec2 a_texCoord; | ||
@@ -35,3 +55,3 @@ attribute vec2 a_position; | ||
| const fragmentTemplate = ({ | ||
| const fragmentSimpleTemplate = ({ | ||
| uniform = '', | ||
@@ -45,2 +65,23 @@ varying = '', | ||
| ${varying} | ||
| ${uniform} | ||
| const vec3 lumcoeff = vec3(0.2125, 0.7154, 0.0721); | ||
| ${constant} | ||
| void main() { | ||
| ${source} | ||
| vec3 color = vec3(0.0); | ||
| float alpha = 1.0; | ||
| ${main} | ||
| gl_FragColor = vec4(color, 1.0) * alpha; | ||
| }`; | ||
| const fragmentMediaTemplate = ({ | ||
| uniform = '', | ||
| varying = '', | ||
| constant = '', | ||
| main = '', | ||
| source = '' | ||
| }) => ` | ||
| precision mediump float; | ||
| ${varying} | ||
| varying vec2 v_texCoord; | ||
@@ -66,10 +107,12 @@ ${uniform} | ||
| * @private | ||
| * @param {WebGLRenderingContext} gl | ||
| * @param effects | ||
| * @param dimensions | ||
| * @param {Object} config | ||
| * @param {WebGLRenderingContext} config.gl | ||
| * @param {Object[]} config.effects | ||
| * @param {{width: number, heignt: number}} [config.dimensions] | ||
| * @param {boolean} [config.noSource] | ||
| * @return {{gl: WebGLRenderingContext, data: kamposSceneData, [dimensions]: {width: number, height: number}}} | ||
| */ | ||
| function init (gl, effects, dimensions) { | ||
| function init ({gl, effects, dimensions, noSource}) { | ||
| const programData = _initProgram(gl, effects); | ||
| const programData = _initProgram(gl, effects, noSource); | ||
@@ -163,7 +206,9 @@ return {gl, data: programData, dimensions: dimensions || {}}; | ||
| // bind the source texture | ||
| gl.bindTexture(gl.TEXTURE_2D, source.texture); | ||
| if ( media && source && source.texture ) { | ||
| // bind the source texture | ||
| gl.bindTexture(gl.TEXTURE_2D, source.texture); | ||
| // read source data into texture | ||
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, media); | ||
| // read source data into texture | ||
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, media); | ||
| } | ||
@@ -215,3 +260,4 @@ // Tell it to use our program (pair of shaders) | ||
| // delete texture | ||
| gl.deleteTexture(source.texture); | ||
| if ( source && source.texture ) | ||
| gl.deleteTexture(source.texture); | ||
@@ -226,4 +272,4 @@ // delete program | ||
| function _initProgram (gl, effects) { | ||
| const source = { | ||
| function _initProgram (gl, effects, noSource=false) { | ||
| const source = noSource || { | ||
| texture: createTexture(gl).texture, | ||
@@ -233,9 +279,11 @@ buffer: null | ||
| // flip Y axis for source texture | ||
| gl.bindTexture(gl.TEXTURE_2D, source.texture); | ||
| gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | ||
| if ( source ) { | ||
| // flip Y axis for source texture | ||
| gl.bindTexture(gl.TEXTURE_2D, source.texture); | ||
| gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | ||
| } | ||
| const data = _mergeEffectsData(effects); | ||
| const vertexSrc = _stringifyShaderSrc(data.vertex, vertexTemplate); | ||
| const fragmentSrc = _stringifyShaderSrc(data.fragment, fragmentTemplate); | ||
| const data = _mergeEffectsData(effects, noSource); | ||
| const vertexSrc = _stringifyShaderSrc(data.vertex, noSource ? vertexSimpleTemplate : vertexMediaTemplate); | ||
| const fragmentSrc = _stringifyShaderSrc(data.fragment, noSource ? fragmentSimpleTemplate : fragmentMediaTemplate); | ||
@@ -266,6 +314,6 @@ // compile the GLSL program | ||
| function _mergeEffectsData (effects) { | ||
| function _mergeEffectsData (effects, noSource=false) { | ||
| return effects.reduce((result, config) => { | ||
| const {attributes = [], uniforms = [], textures = [], varying = {}} = config; | ||
| const merge = shader => Object.keys(config[shader]).forEach(key => { | ||
| const merge = shader => Object.keys(config[shader] || {}).forEach(key => { | ||
| if ( key === 'constant' || key === 'main' || key === 'source' ) { | ||
@@ -302,3 +350,47 @@ result[shader][key] += config[shader][key] + '\n'; | ||
| return result; | ||
| }, { | ||
| }, getEffectDefaults(noSource)); | ||
| } | ||
| function getEffectDefaults (noSource) { | ||
| /* | ||
| * Default uniforms | ||
| */ | ||
| const uniforms = noSource ? [] : [ | ||
| { | ||
| name: 'u_source', | ||
| type: 'i', | ||
| data: [0] | ||
| } | ||
| ]; | ||
| /* | ||
| * Default attributes | ||
| */ | ||
| const attributes = [ | ||
| { | ||
| name: 'a_position', | ||
| data: new Float32Array([ | ||
| -1.0, -1.0, | ||
| -1.0, 1.0, | ||
| 1.0, -1.0, | ||
| 1.0, 1.0]), | ||
| size: 2, | ||
| type: 'FLOAT' | ||
| } | ||
| ]; | ||
| if ( ! noSource ) { | ||
| attributes.push({ | ||
| name: 'a_texCoord', | ||
| data: new Float32Array([ | ||
| 0.0, 0.0, | ||
| 0.0, 1.0, | ||
| 1.0, 0.0, | ||
| 1.0, 1.0]), | ||
| size: 2, | ||
| type: 'FLOAT' | ||
| }); | ||
| } | ||
| return { | ||
| vertex: { | ||
@@ -318,42 +410,9 @@ uniform: {}, | ||
| }, | ||
| attributes, | ||
| uniforms, | ||
| /* | ||
| * Default attributes | ||
| */ | ||
| attributes: [ | ||
| { | ||
| name: 'a_position', | ||
| data: new Float32Array([ | ||
| -1.0, -1.0, | ||
| -1.0, 1.0, | ||
| 1.0, -1.0, | ||
| 1.0, 1.0]), | ||
| size: 2, | ||
| type: 'FLOAT' | ||
| }, | ||
| { | ||
| name: 'a_texCoord', | ||
| data: new Float32Array([ | ||
| 0.0, 0.0, | ||
| 0.0, 1.0, | ||
| 1.0, 0.0, | ||
| 1.0, 1.0]), | ||
| size: 2, | ||
| type: 'FLOAT' | ||
| } | ||
| ], | ||
| /* | ||
| * Default uniforms | ||
| */ | ||
| uniforms: [ | ||
| { | ||
| name: 'u_source', | ||
| type: 'i', | ||
| data: [0] | ||
| } | ||
| ], | ||
| /* | ||
| * Default textures | ||
| */ | ||
| textures: [] | ||
| }); | ||
| }; | ||
| } | ||
@@ -360,0 +419,0 @@ |
@@ -11,4 +11,7 @@ /** | ||
| * @property {boolean} disabled | ||
| * @property {boolean} isLuminance | ||
| * | ||
| * @example | ||
| * @description Multiplies `alpha` value with values read from `mask` media source. | ||
| * | ||
| * @example | ||
| * const img = new Image(); | ||
@@ -30,2 +33,3 @@ * img.src = 'picture.png'; | ||
| u_alphaMaskEnabled: 'bool', | ||
| u_alphaMaskIsLuminance: 'bool', | ||
| u_mask: 'sampler2D' | ||
@@ -35,3 +39,10 @@ }, | ||
| if (u_alphaMaskEnabled) { | ||
| alpha *= texture2D(u_mask, v_alphaMaskTexCoord).a; | ||
| vec4 alphaMaskPixel = texture2D(u_mask, v_alphaMaskTexCoord); | ||
| if (u_alphaMaskIsLuminance) { | ||
| alpha *= dot(lumcoeff, alphaMaskPixel.rgb) * alphaMaskPixel.a; | ||
| } | ||
| else { | ||
| alpha *= alphaMaskPixel.a; | ||
| } | ||
| }` | ||
@@ -51,2 +62,9 @@ }, | ||
| }, | ||
| get isLuminance () { | ||
| return !!this.uniforms[2].data[0]; | ||
| }, | ||
| set isLuminance (toggle) { | ||
| this.uniforms[2].data[0] = +toggle; | ||
| this.textures[0].format = toggle ? 'RGBA' : 'ALPHA'; | ||
| }, | ||
| varying: { | ||
@@ -65,2 +83,7 @@ v_alphaMaskTexCoord: 'vec2' | ||
| data: [1] | ||
| }, | ||
| { | ||
| name: 'u_alphaMaskIsLuminance', | ||
| type: 'i', | ||
| data: [0] | ||
| } | ||
@@ -67,0 +90,0 @@ ], |
@@ -20,3 +20,2 @@ /** | ||
| return { | ||
| vertex: {}, | ||
| fragment: { | ||
@@ -23,0 +22,0 @@ uniform: { |
@@ -10,3 +10,3 @@ /** | ||
| * @property {ArrayBufferView|ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|ImageBitmap} map | ||
| * @property {{x: number, y: number}} scale | ||
| * @property {{x: number?, y: number?}} scale | ||
| * @property {boolean} disabled | ||
@@ -13,0 +13,0 @@ * |
@@ -9,4 +9,4 @@ /** | ||
| * @typedef {Object} duotoneEffect | ||
| * @property {number[]} light Array of 4 numbers normalized (0.0 - 1.0) | ||
| * @property {number[]} dark Array of 4 numbers normalized (0.0 - 1.0) | ||
| * @property {number[]} light Array of 4 numbers, normalized (0.0 - 1.0) | ||
| * @property {number[]} dark Array of 4 numbers, normalized (0.0 - 1.0) | ||
| * @property {boolean} disabled | ||
@@ -19,3 +19,2 @@ * | ||
| return { | ||
| vertex: {}, | ||
| fragment: { | ||
@@ -22,0 +21,0 @@ uniform: { |
+7
-1
@@ -6,2 +6,4 @@ import alphaMask from './effects/alpha-mask'; | ||
| import displacement from './effects/displacement'; | ||
| import perlinNoise from './noise/perlin-noise-3d'; | ||
| import turbulence from './effects/turbulence'; | ||
| import fade from './transitions/fade'; | ||
@@ -18,3 +20,4 @@ import displacementTransition from './transitions/displacement'; | ||
| duotone, | ||
| displacement | ||
| displacement, | ||
| turbulence | ||
| }, | ||
@@ -25,4 +28,7 @@ transitions: { | ||
| }, | ||
| noise: { | ||
| perlinNoise | ||
| }, | ||
| Kampos, | ||
| Ticker | ||
| }; |
+26
-15
| import core from './core'; | ||
| /** | ||
| * Initialize a webgl target with effects. | ||
| * Initialize a WebGL target with effects. | ||
| * | ||
@@ -9,7 +9,7 @@ * @class Kampos | ||
| * @example | ||
| * import {Ticker, Kampos, effects} from 'kampos'; | ||
| * const ticker = new Ticker(); | ||
| * import { Kampos, effects} from 'kampos'; | ||
| * | ||
| * const target = document.querySelector('#canvas'); | ||
| * const hueSat = effects.hueSaturation(); | ||
| * const kampos = new Kampos({ticker, target, effects: [hueSat]}); | ||
| * const kampos = new Kampos({target, effects: [hueSat]}); | ||
| */ | ||
@@ -87,6 +87,6 @@ export default class Kampos { | ||
| /** | ||
| * Initializes an Kampos instance. | ||
| * Initializes a Kampos instance. | ||
| * This is called inside the constructor, | ||
| * but can be called again after effects have changed | ||
| * or after {@link Kampos#desotry()}. | ||
| * or after {@link Kampos#destroy}. | ||
| * | ||
@@ -98,3 +98,3 @@ * @param {kamposConfig} [config] defaults to `this.config` | ||
| config = config || this.config; | ||
| let {target, effects, ticker} = config; | ||
| let {target, effects, ticker, noSource} = config; | ||
@@ -124,3 +124,3 @@ if ( Kampos.preventContextCreation ) | ||
| const {data} = core.init(gl, effects, this.dimensions); | ||
| const {data} = core.init({gl, effects, dimensions: this.dimensions, noSource}); | ||
@@ -189,2 +189,7 @@ this.gl = gl; | ||
| const cb = this.config.beforeDraw; | ||
| if ( cb && cb() === false ) | ||
| return; | ||
| core.draw(this.gl, this.media, this.data, this.dimensions); | ||
@@ -196,5 +201,9 @@ } | ||
| * | ||
| * If using a {@see Ticker} this instance will be added to that {@see Ticker}. | ||
| * If a {@link Ticker} is used, this instance will be added to that {@link Ticker}. | ||
| * | ||
| * @param {function} beforeDraw function to run before each draw call | ||
| */ | ||
| play () { | ||
| play (beforeDraw) { | ||
| this.config.beforeDraw = beforeDraw; | ||
| if ( this.ticker ) { | ||
@@ -224,3 +233,3 @@ if ( this.animationFrameId ) { | ||
| * | ||
| * If using a {@see Ticker} this instance will be removed from that {@see Ticker}. | ||
| * If a {@link Ticker} is used, this instance will be removed from that {@link Ticker}. | ||
| */ | ||
@@ -240,5 +249,5 @@ stop () { | ||
| /** | ||
| * Stops animation loop and frees all resources. | ||
| * Stops the animation loop and frees all resources. | ||
| * | ||
| * @param {boolean} keepState for internal use. | ||
| * @param {boolean} keepState for internal use. | ||
| */ | ||
@@ -329,2 +338,4 @@ destroy (keepState) { | ||
| * @property {Ticker} [ticker] | ||
| * @property {boolean} [noSource] | ||
| * @property {function} [beforeDraw] function to run before each draw call. If it returns `false` {@link kampos#draw} will not be called. | ||
| * @property {function} [onContextLost] | ||
@@ -357,4 +368,4 @@ * @property {function} [onContextRestored] | ||
| * @property {string} [constant] | ||
| * @property {Object} [uniform] mapping name of variable to type | ||
| * @property {Object} [attribute] mapping name of variable to type | ||
| * @property {Object} [uniform] mapping variable name to type | ||
| * @property {Object} [attribute] mapping variable name to type | ||
| */ | ||
@@ -361,0 +372,0 @@ |
+2
-2
| /** | ||
| * Initialize a ticker instance for batching animation of multiple Kampos instances. | ||
| * Initialize a ticker instance for batching animation of multiple {@link Kampos} instances. | ||
| * | ||
@@ -34,3 +34,3 @@ * @class Ticker | ||
| /** | ||
| * Invoke draw() on all instances in the pool. | ||
| * Invoke `.draw()` on all instances in the pool. | ||
| */ | ||
@@ -37,0 +37,0 @@ draw () { |
@@ -12,4 +12,4 @@ /** | ||
| * @property {number} progress number between 0.0 and 1.0 | ||
| * @property {{x: number, y: number}} sourceScale | ||
| * @property {{x: number, y: number}} toScale | ||
| * @property {{x: number?, y: number?}} sourceScale | ||
| * @property {{x: number?, y: number?}} toScale | ||
| * @property {boolean} disabled | ||
@@ -16,0 +16,0 @@ * |
+6
-6
@@ -25,3 +25,3 @@ const core = require('./src/core'); | ||
| const canvas = document.createElement('canvas'); | ||
| const {gl, data} = core.init(core.getWebGLContext(canvas), [brightnessContrast]); | ||
| const {gl, data} = core.init({gl: core.getWebGLContext(canvas), effects: [brightnessContrast]}); | ||
@@ -36,3 +36,3 @@ assert(gl); | ||
| const canvas = document.createElement('canvas'); | ||
| const {gl, data} = core.init(core.getWebGLContext(canvas), [brightnessContrast]); | ||
| const {gl, data} = core.init({gl: core.getWebGLContext(canvas), effects: [brightnessContrast]}); | ||
@@ -70,3 +70,3 @@ assert(gl); | ||
| const initData = core.init(core.getWebGLContext(canvas), [brightnessContrast]); | ||
| const initData = core.init({gl: core.getWebGLContext(canvas), effects: [brightnessContrast]}); | ||
@@ -92,3 +92,3 @@ gl = initData.gl; | ||
| const initData = core.init(core.getWebGLContext(canvas), [brightnessContrast]); | ||
| const initData = core.init({gl: core.getWebGLContext(canvas), effects: [brightnessContrast]}); | ||
@@ -134,3 +134,3 @@ gl = initData.gl; | ||
| core.destroy(gl, scene); | ||
| const initData2fx = core.init(core.getWebGLContext(canvas), [brightnessContrast]); | ||
| const initData2fx = core.init({gl: core.getWebGLContext(canvas), effects: [brightnessContrast]}); | ||
@@ -161,3 +161,3 @@ gl = initData2fx.gl; | ||
| const initData = core.init(core.getWebGLContext(canvas), [brightnessContrast]); | ||
| const initData = core.init({gl: core.getWebGLContext(canvas), effects: [brightnessContrast]}); | ||
@@ -164,0 +164,0 @@ gl = initData.gl; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
11817558
101.79%168
7.69%17147
2.5%78
5.41%55
22.22%