etro 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +2 -0
- package/.github/workflows/nodejs.yml +27 -0
- package/CHANGELOG.md +109 -0
- package/CODE_OF_CONDUCT.md +77 -0
- package/CONTRIBUTING.md +155 -0
- package/LICENSE +674 -0
- package/README.md +57 -0
- package/dist/etro.js +3397 -0
- package/docs/effect.js.html +1215 -0
- package/docs/event.js.html +145 -0
- package/docs/index.html +81 -0
- package/docs/index.js.html +92 -0
- package/docs/layer.js.html +888 -0
- package/docs/module-effect-GaussianBlurComponent.html +345 -0
- package/docs/module-effect.Brightness.html +339 -0
- package/docs/module-effect.Channels.html +319 -0
- package/docs/module-effect.ChromaKey.html +611 -0
- package/docs/module-effect.Contrast.html +339 -0
- package/docs/module-effect.EllipticalMask.html +200 -0
- package/docs/module-effect.GaussianBlur.html +202 -0
- package/docs/module-effect.GaussianBlurHorizontal.html +242 -0
- package/docs/module-effect.GaussianBlurVertical.html +242 -0
- package/docs/module-effect.Pixelate.html +330 -0
- package/docs/module-effect.Shader.html +1227 -0
- package/docs/module-effect.Stack.html +406 -0
- package/docs/module-effect.Transform.Matrix.html +193 -0
- package/docs/module-effect.Transform.html +1174 -0
- package/docs/module-effect.html +148 -0
- package/docs/module-event.html +473 -0
- package/docs/module-index.html +186 -0
- package/docs/module-layer-Media.html +1116 -0
- package/docs/module-layer-MediaMixin.html +164 -0
- package/docs/module-layer.Audio.html +1188 -0
- package/docs/module-layer.Base.html +629 -0
- package/docs/module-layer.Image.html +1421 -0
- package/docs/module-layer.Text.html +1731 -0
- package/docs/module-layer.Video.html +1938 -0
- package/docs/module-layer.Visual.html +1698 -0
- package/docs/module-layer.html +137 -0
- package/docs/module-movie.html +3118 -0
- package/docs/module-util.Color.html +702 -0
- package/docs/module-util.Font.html +395 -0
- package/docs/module-util.html +845 -0
- package/docs/movie.js.html +689 -0
- package/docs/scripts/collapse.js +20 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +83 -0
- package/docs/styles/jsdoc.css +671 -0
- package/docs/styles/prettify.css +79 -0
- package/docs/util.js.html +503 -0
- package/eslint.conf.js +28 -0
- package/eslint.test-conf.js +4 -0
- package/examples/application/readme-screenshot.html +86 -0
- package/examples/application/video-player.html +131 -0
- package/examples/application/webcam.html +28 -0
- package/examples/introduction/audio.html +52 -0
- package/examples/introduction/effects.html +56 -0
- package/examples/introduction/export.html +70 -0
- package/examples/introduction/functions.html +35 -0
- package/examples/introduction/hello-world1.html +33 -0
- package/examples/introduction/hello-world2.html +32 -0
- package/examples/introduction/keyframes.html +67 -0
- package/examples/introduction/media.html +55 -0
- package/examples/introduction/text.html +27 -0
- package/jsdoc.conf.json +3 -0
- package/karma.conf.js +60 -0
- package/package.json +63 -0
- package/private-todo.txt +70 -0
- package/rename-file.sh +18 -0
- package/rename-versions.sh +14 -0
- package/rename.sh +22 -0
- package/rollup.config.js +31 -0
- package/screenshots/2019-08-17_0.png +0 -0
- package/scripts/gen-effect-samples.html +99 -0
- package/scripts/save-effect-samples.js +43 -0
- package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
- package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
- package/spec/assets/effect/original.png +0 -0
- package/spec/assets/effect/pixelate.png +0 -0
- package/spec/assets/effect/transform/multiply.png +0 -0
- package/spec/assets/effect/transform/rotate.png +0 -0
- package/spec/assets/effect/transform/scale-fraction.png +0 -0
- package/spec/assets/effect/transform/scale.png +0 -0
- package/spec/assets/effect/transform/translate-fraction.png +0 -0
- package/spec/assets/effect/transform/translate.png +0 -0
- package/spec/assets/layer/audio.wav +0 -0
- package/spec/assets/layer/image.jpg +0 -0
- package/spec/effect.spec.js +352 -0
- package/spec/event.spec.js +25 -0
- package/spec/layer.spec.js +128 -0
- package/spec/movie.spec.js +154 -0
- package/spec/util.spec.js +285 -0
- package/src/effect.js +1265 -0
- package/src/event.js +78 -0
- package/src/index.js +23 -0
- package/src/layer.js +875 -0
- package/src/movie.js +636 -0
- package/src/util.js +487 -0
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>effect.js - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
|
|
21
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
22
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
23
|
+
<div class="navicon"></div>
|
|
24
|
+
</label>
|
|
25
|
+
|
|
26
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
27
|
+
|
|
28
|
+
<nav >
|
|
29
|
+
|
|
30
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="module-effect.Brightness.html">Brightness</a></li><li><a href="module-effect.Channels.html">Channels</a></li><li><a href="module-effect.ChromaKey.html">ChromaKey</a></li><li><a href="module-effect.Contrast.html">Contrast</a></li><li><a href="module-effect.EllipticalMask.html">EllipticalMask</a></li><li><a href="module-effect.GaussianBlur.html">GaussianBlur</a></li><li><a href="module-effect.GaussianBlurHorizontal.html">GaussianBlurHorizontal</a></li><li><a href="module-effect.GaussianBlurVertical.html">GaussianBlurVertical</a></li><li><a href="module-effect.Pixelate.html">Pixelate</a></li><li><a href="module-effect.Shader.html">Shader</a><ul class='methods'><li data-type='method'><a href="module-effect.Shader.html#._initBuffer">_initBuffer</a></li><li data-type='method'><a href="module-effect.Shader.html#._loadTexture">_loadTexture</a></li><li data-type='method'><a href="module-effect.Shader.html#_prepareValue">_prepareValue</a></li></ul></li><li><a href="module-effect.Stack.html">Stack</a><ul class='methods'><li data-type='method'><a href="module-effect.Stack.html#addEffect">addEffect</a></li></ul></li><li><a href="module-effect.Transform.html">Transform</a><ul class='methods'><li data-type='method'><a href="module-effect.Transform.html#.Matrix#cell">Matrix#cell</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#multiply">Matrix#multiply</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#rotate">Matrix#rotate</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#scale">Matrix#scale</a></li><li data-type='method'><a href="module-effect.Transform.html#.Matrix#translate">Matrix#translate</a></li></ul></li><li><a href="module-effect.Transform.Matrix.html">Matrix</a></li><li><a href="module-effect-GaussianBlurComponent.html">GaussianBlurComponent</a></li><li><a href="module-layer.Audio.html">Audio</a></li><li><a href="module-layer.Base.html">Base</a><ul class='methods'><li data-type='method'><a href="module-layer.Base.html#_render">_render</a></li></ul></li><li><a href="module-layer.Image.html">Image</a></li><li><a href="module-layer.Text.html">Text</a></li><li><a href="module-layer.Video.html">Video</a></li><li><a href="module-layer.Visual.html">Visual</a><ul class='methods'><li data-type='method'><a href="module-layer.Visual.html#_render">_render</a></li><li data-type='method'><a href="module-layer.Visual.html#addEffect">addEffect</a></li></ul></li><li><a href="module-layer-Media.html">Media</a></li><li><a href="module-movie.html">movie</a><ul class='methods'><li data-type='method'><a href="module-movie.html#addEffect">addEffect</a></li><li data-type='method'><a href="module-movie.html#addLayer">addLayer</a></li><li data-type='method'><a href="module-movie.html#pause">pause</a></li><li data-type='method'><a href="module-movie.html#play">play</a></li><li data-type='method'><a href="module-movie.html#publishToLayers">publishToLayers</a></li><li data-type='method'><a href="module-movie.html#record">record</a></li><li data-type='method'><a href="module-movie.html#refresh">refresh</a></li><li data-type='method'><a href="module-movie.html#setCurrentTime">setCurrentTime</a></li><li data-type='method'><a href="module-movie.html#stop">stop</a></li></ul></li><li><a href="module-util.Color.html">Color</a><ul class='methods'><li data-type='method'><a href="module-util.Color.html#toString">toString</a></li></ul></li><li><a href="module-util.Font.html">Font</a><ul class='methods'><li data-type='method'><a href="module-util.Font.html#toString">toString</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-effect.html">effect</a></li><li><a href="module-event.html">event</a><ul class='methods'><li data-type='method'><a href="module-event.html#.publish">publish</a></li><li data-type='method'><a href="module-event.html#.subscribe">subscribe</a></li></ul></li><li><a href="module-index.html">index</a></li><li><a href="module-layer.html">layer</a></li><li><a href="module-movie.html">movie</a><ul class='methods'><li data-type='method'><a href="module-movie.html#addEffect">addEffect</a></li><li data-type='method'><a href="module-movie.html#addLayer">addLayer</a></li><li data-type='method'><a href="module-movie.html#pause">pause</a></li><li data-type='method'><a href="module-movie.html#play">play</a></li><li data-type='method'><a href="module-movie.html#publishToLayers">publishToLayers</a></li><li data-type='method'><a href="module-movie.html#record">record</a></li><li data-type='method'><a href="module-movie.html#refresh">refresh</a></li><li data-type='method'><a href="module-movie.html#setCurrentTime">setCurrentTime</a></li><li data-type='method'><a href="module-movie.html#stop">stop</a></li></ul></li><li><a href="module-util.html">util</a><ul class='methods'><li data-type='method'><a href="module-util.html#.parseColor">parseColor</a></li><li data-type='method'><a href="module-util.html#.parseFont">parseFont</a></li><li data-type='method'><a href="module-util.html#.watchPublic">watchPublic</a></li><li data-type='method'><a href="module-util.html#~isKeyFrames">isKeyFrames</a></li></ul></li></ul><h3>Mixins</h3><ul><li><a href="module-layer-MediaMixin.html">MediaMixin</a></li></ul>
|
|
31
|
+
</nav>
|
|
32
|
+
|
|
33
|
+
<div id="main">
|
|
34
|
+
|
|
35
|
+
<h1 class="page-title">effect.js</h1>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<section>
|
|
44
|
+
<article>
|
|
45
|
+
<pre class="prettyprint source linenums"><code>/**
|
|
46
|
+
* @module effect
|
|
47
|
+
*
|
|
48
|
+
* @todo Investigate why an effect might run once in the beginning even if its layer isn't at the beginning
|
|
49
|
+
* @todo Add audio effect support
|
|
50
|
+
* @todo Move shader source to external files
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { publish, subscribe } from './event.js'
|
|
54
|
+
import { val, watchPublic } from './util.js'
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Any effect that modifies the visual contents of a layer.
|
|
58
|
+
*
|
|
59
|
+
* <em>Note: At this time, simply use the <code>actx</code> property of the movie to add audio nodes to a
|
|
60
|
+
* layer's media. TODO: add more audio support, including more types of audio nodes, probably in a
|
|
61
|
+
* different module.</em>
|
|
62
|
+
*/
|
|
63
|
+
export class Base {
|
|
64
|
+
constructor () {
|
|
65
|
+
const newThis = watchPublic(this) // proxy that will be returned by constructor
|
|
66
|
+
|
|
67
|
+
subscribe(newThis, 'effect.attach', event => {
|
|
68
|
+
newThis._target = event.layer || event.movie // either one or the other (depending on the event caller)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Propogate up to target
|
|
72
|
+
subscribe(newThis, 'effect.change.modify', event => {
|
|
73
|
+
if (!newThis._target) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
const type = `${newThis._target._type}.change.effect.modify`
|
|
77
|
+
publish(newThis._target, type, { ...event, target: newThis._target, source: newThis, type })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return newThis
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// subclasses must implement apply
|
|
84
|
+
/**
|
|
85
|
+
* Apply this effect to a target at the given time
|
|
86
|
+
*
|
|
87
|
+
* @param {module:movie|module:layer.Base} target
|
|
88
|
+
* @param {number} reltime - the movie's current time relative to the layer (will soon be replaced with an instance getter)
|
|
89
|
+
* @abstract
|
|
90
|
+
*/
|
|
91
|
+
apply (target, reltime) {
|
|
92
|
+
throw new Error('No overriding method found or super.apply was called')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get _parent () {
|
|
96
|
+
return this._target
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// id for events (independent of instance, but easy to access when on prototype chain)
|
|
100
|
+
Base.prototype._type = 'effect'
|
|
101
|
+
Base.prototype._publicExcludes = []
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A sequence of effects to apply, treated as one effect. This can be useful for defining reused effect sequences as one effect.
|
|
105
|
+
*/
|
|
106
|
+
export class Stack extends Base {
|
|
107
|
+
constructor (effects) {
|
|
108
|
+
super()
|
|
109
|
+
/**
|
|
110
|
+
* @type module:effect.Base[]
|
|
111
|
+
*/
|
|
112
|
+
this.effects = effects
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Convenience method for chaining
|
|
117
|
+
* @param {module:effect.Base} effect - the effect to append
|
|
118
|
+
*/
|
|
119
|
+
addEffect (effect) {
|
|
120
|
+
this.effects.push(effect)
|
|
121
|
+
return this
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
apply (target, reltime) {
|
|
125
|
+
for (let i = 0; i < this.effects.length; i++) {
|
|
126
|
+
const effect = this.effects[i]
|
|
127
|
+
effect.apply(target, reltime)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A hardware-accelerated pixel mapping
|
|
134
|
+
* @todo can `v_TextureCoord` be replaced by `gl_FragUV`
|
|
135
|
+
*/
|
|
136
|
+
export class Shader extends Base {
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} fragmentSrc
|
|
139
|
+
* @param {object} [userUniforms={}]
|
|
140
|
+
* @param {object[]} [userTextures=[]]
|
|
141
|
+
* @param {object} [sourceTextureOptions={}]
|
|
142
|
+
*/
|
|
143
|
+
constructor (fragmentSrc = Shader._IDENTITY_FRAGMENT_SOURCE, userUniforms = {}, userTextures = [], sourceTextureOptions = {}) {
|
|
144
|
+
super()
|
|
145
|
+
// TODO: split up into multiple methods
|
|
146
|
+
|
|
147
|
+
// Init WebGL
|
|
148
|
+
this._canvas = document.createElement('canvas')
|
|
149
|
+
const gl = this._canvas.getContext('webgl')
|
|
150
|
+
if (gl === null) {
|
|
151
|
+
throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this._program = Shader._initShaderProgram(gl, Shader._VERTEX_SOURCE, fragmentSrc)
|
|
155
|
+
this._buffers = Shader._initRectBuffers(gl)
|
|
156
|
+
|
|
157
|
+
const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
|
|
158
|
+
if (userTextures.length > maxTextures) {
|
|
159
|
+
console.warn('Too many textures!')
|
|
160
|
+
}
|
|
161
|
+
this._userTextures = {}
|
|
162
|
+
for (const name in userTextures) {
|
|
163
|
+
const userOptions = userTextures[name]
|
|
164
|
+
// Apply default options.
|
|
165
|
+
const options = { ...Shader._DEFAULT_TEXTURE_OPTIONS, ...userOptions }
|
|
166
|
+
|
|
167
|
+
if (options.createUniform) {
|
|
168
|
+
// Automatically, create a uniform with the same name as this texture, that points to it.
|
|
169
|
+
// This is an easy way for the user to use custom textures, without having to define multiple properties in the effect object.
|
|
170
|
+
if (userUniforms[name]) {
|
|
171
|
+
throw new Error(`Texture - uniform naming conflict: ${name}!`)
|
|
172
|
+
}
|
|
173
|
+
// Add this as a "user uniform".
|
|
174
|
+
userUniforms[name] = '1i' // texture pointer
|
|
175
|
+
}
|
|
176
|
+
this._userTextures[name] = options
|
|
177
|
+
}
|
|
178
|
+
this._sourceTextureOptions = { ...Shader._DEFAULT_TEXTURE_OPTIONS, ...sourceTextureOptions }
|
|
179
|
+
|
|
180
|
+
this._attribLocations = {
|
|
181
|
+
textureCoord: gl.getAttribLocation(this._program, 'a_TextureCoord')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this._uniformLocations = {
|
|
185
|
+
// modelViewMatrix: gl.getUniformLocation(this._program, "u_ModelViewMatrix"),
|
|
186
|
+
source: gl.getUniformLocation(this._program, 'u_Source'),
|
|
187
|
+
size: gl.getUniformLocation(this._program, 'u_Size')
|
|
188
|
+
}
|
|
189
|
+
// The options value can just be a string equal to the type of the variable, for syntactic sugar.
|
|
190
|
+
// If this is the case, convert it to a real options object.
|
|
191
|
+
this._userUniforms = {}
|
|
192
|
+
for (const name in userUniforms) {
|
|
193
|
+
const val = userUniforms[name]
|
|
194
|
+
this._userUniforms[name] = typeof val === 'string' ? { type: val } : val
|
|
195
|
+
}
|
|
196
|
+
for (const unprefixed in userUniforms) {
|
|
197
|
+
// property => u_Property
|
|
198
|
+
const prefixed = 'u_' + unprefixed.charAt(0).toUpperCase() + (unprefixed.length > 1 ? unprefixed.slice(1) : '')
|
|
199
|
+
this._uniformLocations[unprefixed] = gl.getUniformLocation(this._program, prefixed)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this._gl = gl
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Not needed, right?
|
|
206
|
+
/* watchWebGLOptions() {
|
|
207
|
+
const pubChange = () => {
|
|
208
|
+
this.publish("change", {});
|
|
209
|
+
};
|
|
210
|
+
for (let name in this._userTextures) {
|
|
211
|
+
watch(this, name, pubChange);
|
|
212
|
+
}
|
|
213
|
+
for (let name in this._userUniforms) {
|
|
214
|
+
watch(this, name, pubChange);
|
|
215
|
+
}
|
|
216
|
+
} */
|
|
217
|
+
|
|
218
|
+
apply (target, reltime) {
|
|
219
|
+
// TODO: split up into multiple methods
|
|
220
|
+
const gl = this._gl
|
|
221
|
+
|
|
222
|
+
// TODO: Change target.canvas.width => target.width and see if it breaks anything.
|
|
223
|
+
if (this._canvas.width !== target.canvas.width || this._canvas.height !== target.canvas.height) { // (optimization)
|
|
224
|
+
this._canvas.width = target.canvas.width
|
|
225
|
+
this._canvas.height = target.canvas.height
|
|
226
|
+
|
|
227
|
+
gl.viewport(0, 0, target.canvas.width, target.canvas.height)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
gl.clearColor(0, 0, 0, 0) // clear to transparency; TODO: test
|
|
231
|
+
// gl.clearDepth(1.0); // clear everything
|
|
232
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
233
|
+
gl.enable(gl.BLEND)
|
|
234
|
+
gl.disable(gl.DEPTH_TEST) // gl.depthFunc(gl.LEQUAL);
|
|
235
|
+
|
|
236
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
|
237
|
+
|
|
238
|
+
// Tell WebGL how to pull out the positions from buffer
|
|
239
|
+
{
|
|
240
|
+
const numComponents = 2
|
|
241
|
+
const type = gl.FLOAT // the data in the buffer is 32bit floats
|
|
242
|
+
const normalize = false // don't normalize
|
|
243
|
+
const stride = 0 // how many bytes to get from one set of values to the next
|
|
244
|
+
// 0 = use type and numComponents above
|
|
245
|
+
const offset = 0 // how many bytes inside the buffer to start from
|
|
246
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers.position)
|
|
247
|
+
gl.vertexAttribPointer(
|
|
248
|
+
this._attribLocations.vertexPosition,
|
|
249
|
+
numComponents,
|
|
250
|
+
type,
|
|
251
|
+
normalize,
|
|
252
|
+
stride,
|
|
253
|
+
offset)
|
|
254
|
+
gl.enableVertexAttribArray(
|
|
255
|
+
this._attribLocations.vertexPosition)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// tell webgl how to pull out the texture coordinates from buffer
|
|
259
|
+
{
|
|
260
|
+
const numComponents = 2 // every coordinate composed of 2 values (uv)
|
|
261
|
+
const type = gl.FLOAT // the data in the buffer is 32 bit float
|
|
262
|
+
const normalize = false // don't normalize
|
|
263
|
+
const stride = 0 // how many bytes to get from one set to the next
|
|
264
|
+
const offset = 0 // how many bytes inside the buffer to start from
|
|
265
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers.textureCoord)
|
|
266
|
+
gl.vertexAttribPointer(this._attribLocations.textureCoord, numComponents, type, normalize, stride, offset)
|
|
267
|
+
gl.enableVertexAttribArray(this._attribLocations.textureCoord)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// TODO: figure out which properties should be private / public
|
|
271
|
+
|
|
272
|
+
// Tell WebGL we want to affect texture unit 0
|
|
273
|
+
// Call `activeTexture` before `_loadTexture` so it won't be bound to the last active texture.
|
|
274
|
+
gl.activeTexture(gl.TEXTURE0)
|
|
275
|
+
this._inputTexture = Shader._loadTexture(gl, target.canvas)
|
|
276
|
+
// Bind the texture to texture unit 0
|
|
277
|
+
gl.bindTexture(gl.TEXTURE_2D, this._inputTexture)
|
|
278
|
+
|
|
279
|
+
{
|
|
280
|
+
const i = 0
|
|
281
|
+
for (const name in this._userTextures) {
|
|
282
|
+
const options = this._userTextures[name]
|
|
283
|
+
const source = this[name]
|
|
284
|
+
// Call `activeTexture` before `_loadTexture` so it won't be bound to the last active texture.
|
|
285
|
+
// TODO: investigate better implementation of `_loadTexture`
|
|
286
|
+
gl.activeTexture(gl.TEXTURE0 + Shader.INTERNAL_TEXTURE_UNITS + i) // use the fact that TEXTURE0, TEXTURE1, ... are continuous
|
|
287
|
+
const preparedTex = Shader._loadTexture(gl, val(source, this, reltime), options) // do it every frame to keep updated (I think you need to)
|
|
288
|
+
gl.bindTexture(gl[options.target], preparedTex)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
gl.useProgram(this._program)
|
|
293
|
+
|
|
294
|
+
// Set the shader uniforms
|
|
295
|
+
|
|
296
|
+
// Tell the shader we bound the texture to texture unit 0
|
|
297
|
+
// All base (Shader class) uniforms are optional
|
|
298
|
+
if (this._uniformLocations.source) {
|
|
299
|
+
gl.uniform1i(this._uniformLocations.source, 0)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// All base (Shader class) uniforms are optional
|
|
303
|
+
if (this._uniformLocations.size) {
|
|
304
|
+
gl.uniform2iv(this._uniformLocations.size, [target.width, target.height])
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const unprefixed in this._userUniforms) {
|
|
308
|
+
const options = this._userUniforms[unprefixed]
|
|
309
|
+
const value = val(this[unprefixed], this, reltime)
|
|
310
|
+
const preparedValue = this._prepareValue(val(value, this, reltime), options.type, reltime, options)
|
|
311
|
+
const location = this._uniformLocations[unprefixed]
|
|
312
|
+
gl['uniform' + options.type](location, preparedValue) // haHA JavaScript (`options.type` is "1f", for instance)
|
|
313
|
+
}
|
|
314
|
+
gl.uniform1i(this._uniformLocations.test, 0)
|
|
315
|
+
|
|
316
|
+
{
|
|
317
|
+
const offset = 0
|
|
318
|
+
const vertexCount = 4
|
|
319
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* let ctx = target.cctx || target._movie.cctx, // always render to movie canvas
|
|
323
|
+
movie = target instanceof Movie ? target : target._movie,
|
|
324
|
+
x = val(target.x) || 0, // layer offset
|
|
325
|
+
y = val(target.y) || 0, // layer offset
|
|
326
|
+
width = val(target.width || movie.width),
|
|
327
|
+
height = val(target.height || movie.height);
|
|
328
|
+
|
|
329
|
+
// copy internal image state onto movie
|
|
330
|
+
ctx.drawImage(this._canvas, x, y, width, height); */
|
|
331
|
+
|
|
332
|
+
// clear the target, in case the effect outputs transparent pixels
|
|
333
|
+
target.cctx.clearRect(0, 0, target.canvas.width, target.canvas.height)
|
|
334
|
+
// copy internal image state onto target
|
|
335
|
+
target.cctx.drawImage(this._canvas, 0, 0)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Converts a value of a standard type for javascript to a standard type for GLSL
|
|
340
|
+
* @param value - the raw value to prepare
|
|
341
|
+
* @param {string} outputType - the WebGL type of |value|; example: <code>1f</code> for a float
|
|
342
|
+
* @param {number} reltime - current time, relative to the target
|
|
343
|
+
* @param {object} [options] - Optional config
|
|
344
|
+
*/
|
|
345
|
+
_prepareValue (value, outputType, reltime, options = {}) {
|
|
346
|
+
const def = options.defaultFloatComponent || 0
|
|
347
|
+
if (outputType === '1i') {
|
|
348
|
+
/*
|
|
349
|
+
* Textures are passed to the shader by both providing the texture (with texImage2D)
|
|
350
|
+
* and setting the |sampler| uniform equal to the index of the texture.
|
|
351
|
+
* In etro shader effects, the subclass passes the names of all the textures ot this base class,
|
|
352
|
+
* along with all the names of uniforms. By default, corresponding uniforms (with the same name) are
|
|
353
|
+
* created for each texture for ease of use. You can also define different texture properties in the
|
|
354
|
+
* javascript effect by setting it identical to the property with the passed texture name.
|
|
355
|
+
* In WebGL, it will be set to the same integer texture unit.
|
|
356
|
+
*
|
|
357
|
+
* To do this, test if |value| is identical to a texture.
|
|
358
|
+
* If so, set it to the texture's index, so the shader can use it.
|
|
359
|
+
*/
|
|
360
|
+
let i = 0
|
|
361
|
+
for (const name in this._userTextures) {
|
|
362
|
+
const testValue = val(this[name], this, reltime)
|
|
363
|
+
if (value === testValue) {
|
|
364
|
+
value = Shader.INTERNAL_TEXTURE_UNITS + i // after the internal texture units
|
|
365
|
+
}
|
|
366
|
+
i++
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (outputType === '3fv') {
|
|
371
|
+
// allow 4-component vectors; TODO: why?
|
|
372
|
+
if (Array.isArray(value) && (value.length === 3 || value.length === 4)) {
|
|
373
|
+
return value
|
|
374
|
+
}
|
|
375
|
+
// kind of loose so this can be changed if needed
|
|
376
|
+
if (typeof value === 'object') {
|
|
377
|
+
return [
|
|
378
|
+
value.r !== undefined ? value.r : def,
|
|
379
|
+
value.g !== undefined ? value.g : def,
|
|
380
|
+
value.b !== undefined ? value.b : def
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
throw new Error(`Invalid type: ${outputType} or value: ${value}`)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (outputType === '4fv') {
|
|
388
|
+
if (Array.isArray(value) && value.length === 4) {
|
|
389
|
+
return value
|
|
390
|
+
}
|
|
391
|
+
// kind of loose so this can be changed if needed
|
|
392
|
+
if (typeof value === 'object') {
|
|
393
|
+
return [
|
|
394
|
+
value.r !== undefined ? value.r : def,
|
|
395
|
+
value.g !== undefined ? value.g : def,
|
|
396
|
+
value.b !== undefined ? value.b : def,
|
|
397
|
+
value.a !== undefined ? value.a : def
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
throw new Error(`Invalid type: ${outputType} or value: ${value}`)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return value
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Shader.prototype.get_publicExcludes = () =>
|
|
408
|
+
Shader._initRectBuffers = gl => {
|
|
409
|
+
const position = [
|
|
410
|
+
// the screen/canvas (output)
|
|
411
|
+
-1.0, 1.0,
|
|
412
|
+
1.0, 1.0,
|
|
413
|
+
-1.0, -1.0,
|
|
414
|
+
1.0, -1.0
|
|
415
|
+
]
|
|
416
|
+
const textureCoord = [
|
|
417
|
+
// the texture/canvas (input)
|
|
418
|
+
0.0, 0.0,
|
|
419
|
+
1.0, 0.0,
|
|
420
|
+
0.0, 1.0,
|
|
421
|
+
1.0, 1.0
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
position: Shader._initBuffer(gl, position),
|
|
426
|
+
textureCoord: Shader._initBuffer(gl, textureCoord)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Creates the quad covering the screen
|
|
431
|
+
*/
|
|
432
|
+
Shader._initBuffer = (gl, data) => {
|
|
433
|
+
const buffer = gl.createBuffer()
|
|
434
|
+
|
|
435
|
+
// Select the buffer as the one to apply buffer operations to from here out.
|
|
436
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
437
|
+
|
|
438
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW)
|
|
439
|
+
|
|
440
|
+
return buffer
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Creates a webgl texture from the source.
|
|
444
|
+
* @param {object} [options] - optional WebGL config for texture
|
|
445
|
+
* @param {number} [options.target=gl.TEXTURE_2D]
|
|
446
|
+
* @param {number} [options.level=0]
|
|
447
|
+
* @param {number} [options.internalFormat=gl.RGBA]
|
|
448
|
+
* @param {number} [options.srcFormat=gl.RGBA]
|
|
449
|
+
* @param {number} [options.srcType=gl.UNSIGNED_BYTE]
|
|
450
|
+
* @param {number} [options.minFilter=gl.LINEAR]
|
|
451
|
+
* @param {number} [options.magFilter=gl.LINEAR]
|
|
452
|
+
*/
|
|
453
|
+
Shader._loadTexture = (gl, source, options = {}) => {
|
|
454
|
+
options = { ...Shader._DEFAULT_TEXTURE_OPTIONS, ...options } // Apply default options, just in case.
|
|
455
|
+
const target = gl[options.target] // When creating the option, the user can't access `gl` so access it here.
|
|
456
|
+
const level = options.level
|
|
457
|
+
const internalFormat = gl[options.internalFormat]
|
|
458
|
+
const srcFormat = gl[options.srcFormat]
|
|
459
|
+
const srcType = gl[options.srcType]
|
|
460
|
+
const minFilter = gl[options.minFilter]
|
|
461
|
+
const magFilter = gl[options.magFilter]
|
|
462
|
+
// TODO: figure out how wrap-s and wrap-t interact with mipmaps
|
|
463
|
+
// (for legacy support)
|
|
464
|
+
// let wrapS = options.wrapS ? options.wrapS : gl.CLAMP_TO_EDGE,
|
|
465
|
+
// wrapT = options.wrapT ? options.wrapT : gl.CLAMP_TO_EDGE;
|
|
466
|
+
|
|
467
|
+
const tex = gl.createTexture()
|
|
468
|
+
gl.bindTexture(target, tex)
|
|
469
|
+
|
|
470
|
+
// TODO: figure out how this works with layer width/height
|
|
471
|
+
|
|
472
|
+
// TODO: support 3d textures (change texImage2D)
|
|
473
|
+
// set to `source`
|
|
474
|
+
gl.texImage2D(target, level, internalFormat, srcFormat, srcType, source)
|
|
475
|
+
|
|
476
|
+
// WebGL1 has different requirements for power of 2 images
|
|
477
|
+
// vs non power of 2 images so check if the image is a
|
|
478
|
+
// power of 2 in both dimensions.
|
|
479
|
+
// Get dimensions by using the fact that all valid inputs for
|
|
480
|
+
// texImage2D must have `width` and `height` properties except
|
|
481
|
+
// videos, which have `videoWidth` and `videoHeight` instead
|
|
482
|
+
// and `ArrayBufferView`, which is one dimensional (so don't
|
|
483
|
+
// worry about mipmaps)
|
|
484
|
+
const w = target instanceof HTMLVideoElement ? target.videoWidth : target.width
|
|
485
|
+
const h = target instanceof HTMLVideoElement ? target.videoHeight : target.height
|
|
486
|
+
if ((w && isPowerOf2(w)) && (h && isPowerOf2(h))) {
|
|
487
|
+
// Yes, it's a power of 2. Generate mips.
|
|
488
|
+
gl.generateMipmap(target)
|
|
489
|
+
} else {
|
|
490
|
+
// No, it's not a power of 2. Turn off mips and set
|
|
491
|
+
// wrapping to clamp to edge
|
|
492
|
+
gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
493
|
+
gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
494
|
+
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minFilter)
|
|
495
|
+
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magFilter)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return tex
|
|
499
|
+
}
|
|
500
|
+
const isPowerOf2 = value => (value && (value - 1)) === 0
|
|
501
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
|
|
502
|
+
Shader._initShaderProgram = (gl, vertexSrc, fragmentSrc) => {
|
|
503
|
+
const vertexShader = Shader._loadShader(gl, gl.VERTEX_SHADER, vertexSrc)
|
|
504
|
+
const fragmentShader = Shader._loadShader(gl, gl.FRAGMENT_SHADER, fragmentSrc)
|
|
505
|
+
|
|
506
|
+
const shaderProgram = gl.createProgram()
|
|
507
|
+
gl.attachShader(shaderProgram, vertexShader)
|
|
508
|
+
gl.attachShader(shaderProgram, fragmentShader)
|
|
509
|
+
gl.linkProgram(shaderProgram)
|
|
510
|
+
|
|
511
|
+
// check program creation status
|
|
512
|
+
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
|
513
|
+
console.warn('Unable to link shader program: ' + gl.getProgramInfoLog(shaderProgram))
|
|
514
|
+
return null
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return shaderProgram
|
|
518
|
+
}
|
|
519
|
+
Shader._loadShader = (gl, type, source) => {
|
|
520
|
+
const shader = gl.createShader(type)
|
|
521
|
+
gl.shaderSource(shader, source)
|
|
522
|
+
gl.compileShader(shader)
|
|
523
|
+
|
|
524
|
+
// check compile status
|
|
525
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
526
|
+
console.warn('An error occured compiling shader: ' + gl.getShaderInfoLog(shader))
|
|
527
|
+
gl.deleteShader(shader)
|
|
528
|
+
return null
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return shader
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* WebGL texture units consumed by <code>Shader</code>
|
|
535
|
+
*/
|
|
536
|
+
Shader.INTERNAL_TEXTURE_UNITS = 1
|
|
537
|
+
Shader._DEFAULT_TEXTURE_OPTIONS = {
|
|
538
|
+
createUniform: true,
|
|
539
|
+
target: 'TEXTURE_2D',
|
|
540
|
+
level: 0,
|
|
541
|
+
internalFormat: 'RGBA',
|
|
542
|
+
srcFormat: 'RGBA',
|
|
543
|
+
srcType: 'UNSIGNED_BYTE',
|
|
544
|
+
minFilter: 'LINEAR',
|
|
545
|
+
magFilter: 'LINEAR'
|
|
546
|
+
}
|
|
547
|
+
Shader._VERTEX_SOURCE = `
|
|
548
|
+
attribute vec4 a_VertexPosition;
|
|
549
|
+
attribute vec2 a_TextureCoord;
|
|
550
|
+
|
|
551
|
+
varying highp vec2 v_TextureCoord;
|
|
552
|
+
|
|
553
|
+
void main() {
|
|
554
|
+
// no need for projection or model-view matrices, since we're just rendering a rectangle
|
|
555
|
+
// that fills the screen (see position values)
|
|
556
|
+
gl_Position = a_VertexPosition;
|
|
557
|
+
v_TextureCoord = a_TextureCoord;
|
|
558
|
+
}
|
|
559
|
+
`
|
|
560
|
+
Shader._IDENTITY_FRAGMENT_SOURCE = `
|
|
561
|
+
precision mediump float;
|
|
562
|
+
|
|
563
|
+
uniform sampler2D u_Source;
|
|
564
|
+
uniform float u_Brightness;
|
|
565
|
+
|
|
566
|
+
varying highp vec2 v_TextureCoord;
|
|
567
|
+
|
|
568
|
+
void main() {
|
|
569
|
+
gl_FragColor = texture2D(u_Source, v_TextureCoord);
|
|
570
|
+
}
|
|
571
|
+
`
|
|
572
|
+
|
|
573
|
+
/* COLOR & TRANSPARENCY */
|
|
574
|
+
// TODO: move shader source code to external .js files (with exports)
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Changes the brightness
|
|
578
|
+
*/
|
|
579
|
+
export class Brightness extends Shader {
|
|
580
|
+
/**
|
|
581
|
+
* @param {number} [brightness=0] - the value to add to each pixel's channels [-255, 255]
|
|
582
|
+
*/
|
|
583
|
+
constructor (brightness = 0.0) {
|
|
584
|
+
super(`
|
|
585
|
+
precision mediump float;
|
|
586
|
+
|
|
587
|
+
uniform sampler2D u_Source;
|
|
588
|
+
uniform float u_Brightness;
|
|
589
|
+
|
|
590
|
+
varying highp vec2 v_TextureCoord;
|
|
591
|
+
|
|
592
|
+
void main() {
|
|
593
|
+
vec4 color = texture2D(u_Source, v_TextureCoord);
|
|
594
|
+
vec3 rgb = clamp(color.rgb + u_Brightness / 255.0, 0.0, 1.0);
|
|
595
|
+
gl_FragColor = vec4(rgb, color.a);
|
|
596
|
+
}
|
|
597
|
+
`, {
|
|
598
|
+
brightness: '1f'
|
|
599
|
+
})
|
|
600
|
+
/**
|
|
601
|
+
* The value to add to each pixel's channels [-255, 255]
|
|
602
|
+
* @type number
|
|
603
|
+
*/
|
|
604
|
+
this.brightness = brightness
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Changes the contrast
|
|
610
|
+
*/
|
|
611
|
+
export class Contrast extends Shader {
|
|
612
|
+
/**
|
|
613
|
+
* @param {number} [contrast=1] - the contrast multiplier
|
|
614
|
+
*/
|
|
615
|
+
constructor (contrast = 1.0) {
|
|
616
|
+
super(`
|
|
617
|
+
precision mediump float;
|
|
618
|
+
|
|
619
|
+
uniform sampler2D u_Source;
|
|
620
|
+
uniform float u_Contrast;
|
|
621
|
+
|
|
622
|
+
varying highp vec2 v_TextureCoord;
|
|
623
|
+
|
|
624
|
+
void main() {
|
|
625
|
+
vec4 color = texture2D(u_Source, v_TextureCoord);
|
|
626
|
+
vec3 rgb = clamp(u_Contrast * (color.rgb - 0.5) + 0.5, 0.0, 1.0);
|
|
627
|
+
gl_FragColor = vec4(rgb, color.a);
|
|
628
|
+
}
|
|
629
|
+
`, {
|
|
630
|
+
contrast: '1f'
|
|
631
|
+
})
|
|
632
|
+
/**
|
|
633
|
+
* The contrast multiplier
|
|
634
|
+
* @type number
|
|
635
|
+
*/
|
|
636
|
+
this.contrast = contrast
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Multiplies each channel by a different number
|
|
642
|
+
*/
|
|
643
|
+
export class Channels extends Shader {
|
|
644
|
+
/**
|
|
645
|
+
* @param {module:util.Color} factors - channel factors, each defaulting to 1
|
|
646
|
+
*/
|
|
647
|
+
constructor (factors = {}) {
|
|
648
|
+
super(`
|
|
649
|
+
precision mediump float;
|
|
650
|
+
|
|
651
|
+
uniform sampler2D u_Source;
|
|
652
|
+
uniform vec4 u_Factors;
|
|
653
|
+
|
|
654
|
+
varying highp vec2 v_TextureCoord;
|
|
655
|
+
|
|
656
|
+
void main() {
|
|
657
|
+
vec4 color = texture2D(u_Source, v_TextureCoord);
|
|
658
|
+
gl_FragColor = clamp(u_Factors * color, 0.0, 1.0);
|
|
659
|
+
}
|
|
660
|
+
`, {
|
|
661
|
+
factors: { type: '4fv', defaultFloatComponent: 1 }
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Channel factors, each defaulting to 1
|
|
666
|
+
* @type module:util.Color
|
|
667
|
+
*/
|
|
668
|
+
this.factors = factors
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Reduces alpha for pixels which are close to a specified target color
|
|
674
|
+
*/
|
|
675
|
+
export class ChromaKey extends Shader {
|
|
676
|
+
/**
|
|
677
|
+
* @param {module:util.Color} [target={r: 0, g: 0, b: 0}] - the color to remove
|
|
678
|
+
* @param {number} [threshold=0] - how much error is allowed
|
|
679
|
+
* @param {boolean} [interpolate=false] - true value to interpolate the alpha channel,
|
|
680
|
+
* or false value for no smoothing (i.e. 255 or 0 alpha)
|
|
681
|
+
* @param {number} [smoothingSharpness=0] - a modifier to lessen the smoothing range, if applicable
|
|
682
|
+
* @todo Use <code>smoothingSharpness</code>
|
|
683
|
+
*/
|
|
684
|
+
constructor (target = { r: 0, g: 0, b: 0 }, threshold = 0, interpolate = false/*, smoothingSharpness=0 */) {
|
|
685
|
+
super(`
|
|
686
|
+
precision mediump float;
|
|
687
|
+
|
|
688
|
+
uniform sampler2D u_Source;
|
|
689
|
+
uniform vec3 u_Target;
|
|
690
|
+
uniform float u_Threshold;
|
|
691
|
+
uniform bool u_Interpolate;
|
|
692
|
+
|
|
693
|
+
varying highp vec2 v_TextureCoord;
|
|
694
|
+
|
|
695
|
+
void main() {
|
|
696
|
+
vec4 color = texture2D(u_Source, v_TextureCoord);
|
|
697
|
+
float alpha = color.a;
|
|
698
|
+
vec3 dist = abs(color.rgb - u_Target / 255.0);
|
|
699
|
+
if (!u_Interpolate) {
|
|
700
|
+
// Standard way that most video editors probably use (all-or-nothing method)
|
|
701
|
+
float thresh = u_Threshold / 255.0;
|
|
702
|
+
bool transparent = dist.r <= thresh && dist.g <= thresh && dist.b <= thresh;
|
|
703
|
+
if (transparent)
|
|
704
|
+
alpha = 0.0;
|
|
705
|
+
} else {
|
|
706
|
+
/*
|
|
707
|
+
better way IMHO:
|
|
708
|
+
Take the average of the absolute differences between the pixel and the target for each channel
|
|
709
|
+
*/
|
|
710
|
+
float transparency = (dist.r + dist.g + dist.b) / 3.0;
|
|
711
|
+
// TODO: custom or variety of interpolation methods
|
|
712
|
+
alpha = transparency;
|
|
713
|
+
}
|
|
714
|
+
gl_FragColor = vec4(color.rgb, alpha);
|
|
715
|
+
}
|
|
716
|
+
`, {
|
|
717
|
+
target: '3fv',
|
|
718
|
+
threshold: '1f',
|
|
719
|
+
interpolate: '1i'
|
|
720
|
+
})
|
|
721
|
+
/**
|
|
722
|
+
* The color to remove
|
|
723
|
+
* @type module:util.Color
|
|
724
|
+
*/
|
|
725
|
+
this.target = target
|
|
726
|
+
/**
|
|
727
|
+
* How much error is alloed
|
|
728
|
+
* @type number
|
|
729
|
+
*/
|
|
730
|
+
this.threshold = threshold
|
|
731
|
+
/**
|
|
732
|
+
* True value to interpolate the alpha channel,
|
|
733
|
+
* or false value for no smoothing (i.e. 255 or 0 alpha)
|
|
734
|
+
* @type boolean
|
|
735
|
+
*/
|
|
736
|
+
this.interpolate = interpolate
|
|
737
|
+
// this.smoothingSharpness = smoothingSharpness;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/* BLUR */
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Applies a Gaussian blur
|
|
745
|
+
*
|
|
746
|
+
* @todo Improve performance
|
|
747
|
+
* @todo Make sure this is truly gaussian even though it doens't require a standard deviation
|
|
748
|
+
*/
|
|
749
|
+
export class GaussianBlur extends Stack {
|
|
750
|
+
constructor (radius) {
|
|
751
|
+
// Divide into two shader effects (use the fact that gaussian blurring can be split into components for performance benefits)
|
|
752
|
+
super([
|
|
753
|
+
new GaussianBlurHorizontal(radius),
|
|
754
|
+
new GaussianBlurVertical(radius)
|
|
755
|
+
])
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Render Gaussian kernel to a canvas for use in shader.
|
|
760
|
+
* @param {number[]} kernel
|
|
761
|
+
* @private
|
|
762
|
+
*
|
|
763
|
+
* @return {HTMLCanvasElement}
|
|
764
|
+
*/
|
|
765
|
+
GaussianBlur.render1DKernel = kernel => {
|
|
766
|
+
// TODO: Use Float32Array instead of canvas.
|
|
767
|
+
// init canvas
|
|
768
|
+
const canvas = document.createElement('canvas')
|
|
769
|
+
canvas.width = kernel.length
|
|
770
|
+
canvas.height = 1 // 1-dimensional
|
|
771
|
+
const ctx = canvas.getContext('2d')
|
|
772
|
+
|
|
773
|
+
// draw to canvas
|
|
774
|
+
const imageData = ctx.createImageData(canvas.width, canvas.height)
|
|
775
|
+
for (let i = 0; i < kernel.length; i++) {
|
|
776
|
+
imageData.data[4 * i + 0] = 255 * kernel[i] // Use red channel to store distribution weights.
|
|
777
|
+
imageData.data[4 * i + 1] = 0 // Clear all other channels.
|
|
778
|
+
imageData.data[4 * i + 2] = 0
|
|
779
|
+
imageData.data[4 * i + 3] = 255
|
|
780
|
+
}
|
|
781
|
+
ctx.putImageData(imageData, 0, 0)
|
|
782
|
+
|
|
783
|
+
return canvas
|
|
784
|
+
}
|
|
785
|
+
GaussianBlur.gen1DKernel = radius => {
|
|
786
|
+
const pascal = GaussianBlur.genPascalRow(2 * radius + 1)
|
|
787
|
+
// don't use `reduce` and `map` (overhead?)
|
|
788
|
+
let sum = 0
|
|
789
|
+
for (let i = 0; i < pascal.length; i++) {
|
|
790
|
+
sum += pascal[i]
|
|
791
|
+
}
|
|
792
|
+
for (let i = 0; i < pascal.length; i++) {
|
|
793
|
+
pascal[i] /= sum
|
|
794
|
+
}
|
|
795
|
+
return pascal
|
|
796
|
+
}
|
|
797
|
+
GaussianBlur.genPascalRow = index => {
|
|
798
|
+
if (index < 0) {
|
|
799
|
+
throw new Error(`Invalid index ${index}`)
|
|
800
|
+
}
|
|
801
|
+
let currRow = [1]
|
|
802
|
+
for (let i = 1; i < index; i++) {
|
|
803
|
+
const nextRow = []
|
|
804
|
+
nextRow.length = currRow.length + 1
|
|
805
|
+
// edges are always 1's
|
|
806
|
+
nextRow[0] = nextRow[nextRow.length - 1] = 1
|
|
807
|
+
for (let j = 1; j < nextRow.length - 1; j++) {
|
|
808
|
+
nextRow[j] = currRow[j - 1] + currRow[j]
|
|
809
|
+
}
|
|
810
|
+
currRow = nextRow
|
|
811
|
+
}
|
|
812
|
+
return currRow
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Shared class for both horizontal and vertical gaussian blur classes.
|
|
817
|
+
* @todo If radius == 0, don't affect the image (right now, the image goes black).
|
|
818
|
+
*/
|
|
819
|
+
class GaussianBlurComponent extends Shader {
|
|
820
|
+
/**
|
|
821
|
+
* @param {string} src - fragment src code specific to which component (horizontal or vertical)
|
|
822
|
+
* @param {number} radius
|
|
823
|
+
*/
|
|
824
|
+
constructor (src, radius) {
|
|
825
|
+
super(src, {
|
|
826
|
+
radius: '1i'
|
|
827
|
+
}, {
|
|
828
|
+
shape: { minFilter: 'NEAREST', magFilter: 'NEAREST' }
|
|
829
|
+
})
|
|
830
|
+
/**
|
|
831
|
+
* @type number
|
|
832
|
+
*/
|
|
833
|
+
this.radius = radius
|
|
834
|
+
this._radiusCache = undefined
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
apply (target, reltime) {
|
|
838
|
+
const radiusVal = val(this.radius, this, reltime)
|
|
839
|
+
if (radiusVal !== this._radiusCache) {
|
|
840
|
+
// Regenerate gaussian distribution.
|
|
841
|
+
this.shape = GaussianBlur.render1DKernel(
|
|
842
|
+
GaussianBlur.gen1DKernel(radiusVal)
|
|
843
|
+
) // distribution canvas
|
|
844
|
+
}
|
|
845
|
+
this._radiusCache = radiusVal
|
|
846
|
+
|
|
847
|
+
super.apply(target, reltime)
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Horizontal component of gaussian blur
|
|
853
|
+
*/
|
|
854
|
+
export class GaussianBlurHorizontal extends GaussianBlurComponent {
|
|
855
|
+
/**
|
|
856
|
+
* @param {number} radius
|
|
857
|
+
*/
|
|
858
|
+
constructor (radius) {
|
|
859
|
+
super(`
|
|
860
|
+
#define MAX_RADIUS 250
|
|
861
|
+
|
|
862
|
+
precision mediump float;
|
|
863
|
+
|
|
864
|
+
uniform sampler2D u_Source;
|
|
865
|
+
uniform ivec2 u_Size; // pixel dimensions of input and output
|
|
866
|
+
uniform sampler2D u_Shape; // pseudo one-dimension of blur distribution (would be 1D but webgl doesn't support it)
|
|
867
|
+
uniform int u_Radius; // TODO: support floating-point radii
|
|
868
|
+
|
|
869
|
+
varying highp vec2 v_TextureCoord;
|
|
870
|
+
|
|
871
|
+
void main() {
|
|
872
|
+
vec4 avg = vec4(0.0);
|
|
873
|
+
// GLSL can only use constants in for-loop declaration, so start at zero, and stop before 2 * u_Radius + 1,
|
|
874
|
+
// opposed to starting at -u_Radius and stopping _at_ +u_Radius.
|
|
875
|
+
for (int i = 0; i < 2 * MAX_RADIUS + 1; i++) {
|
|
876
|
+
if (i >= 2 * u_Radius + 1)
|
|
877
|
+
break; // GLSL can only use constants in for-loop declaration, so we break here.
|
|
878
|
+
// u_Radius is the width of u_Shape, by definition
|
|
879
|
+
float weight = texture2D(u_Shape, vec2(float(i) / float(2 * u_Radius + 1), 0.0)).r; // TODO: use single-channel format
|
|
880
|
+
vec4 sample = texture2D(u_Source, v_TextureCoord + vec2(i - u_Radius, 0.0) / vec2(u_Size));
|
|
881
|
+
avg += weight * sample;
|
|
882
|
+
}
|
|
883
|
+
gl_FragColor = avg;
|
|
884
|
+
}
|
|
885
|
+
`, radius)
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Vertical component of gaussian blur
|
|
891
|
+
*/
|
|
892
|
+
export class GaussianBlurVertical extends GaussianBlurComponent {
|
|
893
|
+
/**
|
|
894
|
+
* @param {number} radius
|
|
895
|
+
*/
|
|
896
|
+
constructor (radius) {
|
|
897
|
+
super(`
|
|
898
|
+
#define MAX_RADIUS 250
|
|
899
|
+
|
|
900
|
+
precision mediump float;
|
|
901
|
+
|
|
902
|
+
uniform sampler2D u_Source;
|
|
903
|
+
uniform ivec2 u_Size; // pixel dimensions of input and output
|
|
904
|
+
uniform sampler2D u_Shape; // pseudo one-dimension of blur distribution (would be 1D but webgl doesn't support it)
|
|
905
|
+
uniform int u_Radius; // TODO: support floating-point radii
|
|
906
|
+
|
|
907
|
+
varying highp vec2 v_TextureCoord;
|
|
908
|
+
|
|
909
|
+
void main() {
|
|
910
|
+
vec4 avg = vec4(0.0);
|
|
911
|
+
// GLSL can only use constants in for-loop declaration, so start at zero, and stop before 2 * u_Radius + 1,
|
|
912
|
+
// opposed to starting at -u_Radius and stopping _at_ +u_Radius.
|
|
913
|
+
for (int i = 0; i < 2 * MAX_RADIUS + 1; i++) {
|
|
914
|
+
if (i >= 2 * u_Radius + 1)
|
|
915
|
+
break; // GLSL can only use constants in for-loop declaration, so we break here.
|
|
916
|
+
// u_Radius is the width of u_Shape, by definition
|
|
917
|
+
float weight = texture2D(u_Shape, vec2(float(i) / float(2 * u_Radius + 1), 0.0)).r; // TODO: use single-channel format
|
|
918
|
+
vec4 sample = texture2D(u_Source, v_TextureCoord + vec2(0.0, i - u_Radius) / vec2(u_Size));
|
|
919
|
+
avg += weight * sample;
|
|
920
|
+
}
|
|
921
|
+
gl_FragColor = avg;
|
|
922
|
+
}
|
|
923
|
+
`, radius)
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Makes the target look pixelated
|
|
929
|
+
* @todo just resample with NEAREST interpolation? but how?
|
|
930
|
+
*/
|
|
931
|
+
export class Pixelate extends Shader {
|
|
932
|
+
/**
|
|
933
|
+
* @param {number} pixelSize
|
|
934
|
+
*/
|
|
935
|
+
constructor (pixelSize = 1) {
|
|
936
|
+
super(`
|
|
937
|
+
precision mediump float;
|
|
938
|
+
|
|
939
|
+
uniform sampler2D u_Source;
|
|
940
|
+
uniform ivec2 u_Size;
|
|
941
|
+
uniform int u_PixelSize;
|
|
942
|
+
|
|
943
|
+
varying highp vec2 v_TextureCoord;
|
|
944
|
+
|
|
945
|
+
void main() {
|
|
946
|
+
// Floor to nearest pixel (times pixel size), not nearest edge of screen
|
|
947
|
+
ivec2 loc = ivec2(vec2(u_Size) * v_TextureCoord); // screen location
|
|
948
|
+
|
|
949
|
+
int ps = u_PixelSize;
|
|
950
|
+
vec2 flooredTexCoord = float(ps) * floor(vec2(loc) / float(ps))
|
|
951
|
+
/ vec2(u_Size);
|
|
952
|
+
gl_FragColor = texture2D(u_Source, flooredTexCoord);
|
|
953
|
+
}
|
|
954
|
+
`, {
|
|
955
|
+
pixelSize: '1i'
|
|
956
|
+
})
|
|
957
|
+
/**
|
|
958
|
+
* @type number
|
|
959
|
+
*/
|
|
960
|
+
this.pixelSize = pixelSize
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
apply (target, reltime) {
|
|
964
|
+
const ps = val(this.pixelSize, target, reltime)
|
|
965
|
+
if (ps % 1 !== 0 || ps < 0) {
|
|
966
|
+
throw new Error('Pixel size must be a nonnegative integer')
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
super.apply(target, reltime)
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// TODO: implement directional blur
|
|
974
|
+
// TODO: implement radial blur
|
|
975
|
+
// TODO: implement zoom blur
|
|
976
|
+
|
|
977
|
+
/* DISTORTION */
|
|
978
|
+
/**
|
|
979
|
+
* Transforms a layer or movie using a transformation matrix. Use {@link Transform.Matrix}
|
|
980
|
+
* to either A) calculate those values based on a series of translations, scalings and rotations)
|
|
981
|
+
* or B) input the matrix values directly, using the optional argument in the constructor.
|
|
982
|
+
*/
|
|
983
|
+
export class Transform extends Base {
|
|
984
|
+
/**
|
|
985
|
+
* @param {module:effect.Transform.Matrix} matrix - how to transform the target
|
|
986
|
+
*/
|
|
987
|
+
constructor (matrix) {
|
|
988
|
+
super()
|
|
989
|
+
/**
|
|
990
|
+
* How to transform the target
|
|
991
|
+
* @type module:effect.Transform.Matrix
|
|
992
|
+
*/
|
|
993
|
+
this.matrix = matrix
|
|
994
|
+
this._tmpMatrix = new Transform.Matrix()
|
|
995
|
+
this._tmpCanvas = document.createElement('canvas')
|
|
996
|
+
this._tmpCtx = this._tmpCanvas.getContext('2d')
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
apply (target, reltime) {
|
|
1000
|
+
if (target.canvas.width !== this._tmpCanvas.width) {
|
|
1001
|
+
this._tmpCanvas.width = target.canvas.width
|
|
1002
|
+
}
|
|
1003
|
+
if (target.canvas.height !== this._tmpCanvas.height) {
|
|
1004
|
+
this._tmpCanvas.height = target.canvas.height
|
|
1005
|
+
}
|
|
1006
|
+
this._tmpMatrix.data = val(this.matrix.data, target, reltime) // use data, since that's the underlying storage
|
|
1007
|
+
|
|
1008
|
+
this._tmpCtx.setTransform(
|
|
1009
|
+
this._tmpMatrix.a, this._tmpMatrix.b, this._tmpMatrix.c,
|
|
1010
|
+
this._tmpMatrix.d, this._tmpMatrix.e, this._tmpMatrix.f
|
|
1011
|
+
)
|
|
1012
|
+
this._tmpCtx.drawImage(target.canvas, 0, 0)
|
|
1013
|
+
// Assume it was identity for now
|
|
1014
|
+
this._tmpCtx.setTransform(1, 0, 0, 0, 1, 0, 0, 0, 1)
|
|
1015
|
+
target.cctx.clearRect(0, 0, target.canvas.width, target.canvas.height)
|
|
1016
|
+
target.cctx.drawImage(this._tmpCanvas, 0, 0)
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* @class
|
|
1021
|
+
* A 3x3 matrix for storing 2d transformations
|
|
1022
|
+
*/
|
|
1023
|
+
Transform.Matrix = class Matrix {
|
|
1024
|
+
constructor (data) {
|
|
1025
|
+
this.data = data || [
|
|
1026
|
+
1, 0, 0,
|
|
1027
|
+
0, 1, 0,
|
|
1028
|
+
0, 0, 1
|
|
1029
|
+
]
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
identity () {
|
|
1033
|
+
for (let i = 0; i < this.data.length; i++) {
|
|
1034
|
+
this.data[i] = Transform.Matrix.IDENTITY.data[i]
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
return this
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* @param {number} x
|
|
1042
|
+
* @param {number} y
|
|
1043
|
+
* @param {number} [val]
|
|
1044
|
+
*/
|
|
1045
|
+
cell (x, y, val) {
|
|
1046
|
+
if (val !== undefined) {
|
|
1047
|
+
this.data[3 * y + x] = val
|
|
1048
|
+
}
|
|
1049
|
+
return this.data[3 * y + x]
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/* For canvas context setTransform */
|
|
1053
|
+
get a () {
|
|
1054
|
+
return this.data[0]
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
get b () {
|
|
1058
|
+
return this.data[3]
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
get c () {
|
|
1062
|
+
return this.data[1]
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
get d () {
|
|
1066
|
+
return this.data[4]
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
get e () {
|
|
1070
|
+
return this.data[2]
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
get f () {
|
|
1074
|
+
return this.data[5]
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Combines <code>this</code> with another matrix <code>other</code>
|
|
1079
|
+
* @param other
|
|
1080
|
+
*/
|
|
1081
|
+
multiply (other) {
|
|
1082
|
+
// copy to temporary matrix to avoid modifying `this` while reading from it
|
|
1083
|
+
// http://www.informit.com/articles/article.aspx?p=98117&seqNum=4
|
|
1084
|
+
for (let x = 0; x < 3; x++) {
|
|
1085
|
+
for (let y = 0; y < 3; y++) {
|
|
1086
|
+
let sum = 0
|
|
1087
|
+
for (let i = 0; i < 3; i++) {
|
|
1088
|
+
sum += this.cell(x, i) * other.cell(i, y)
|
|
1089
|
+
}
|
|
1090
|
+
TMP_MATRIX.cell(x, y, sum)
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
// copy data from TMP_MATRIX to this
|
|
1094
|
+
for (let i = 0; i < TMP_MATRIX.data.length; i++) {
|
|
1095
|
+
this.data[i] = TMP_MATRIX.data[i]
|
|
1096
|
+
}
|
|
1097
|
+
return this
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* @param {number} x
|
|
1102
|
+
* @param {number} y
|
|
1103
|
+
*/
|
|
1104
|
+
translate (x, y) {
|
|
1105
|
+
this.multiply(new Transform.Matrix([
|
|
1106
|
+
1, 0, x,
|
|
1107
|
+
0, 1, y,
|
|
1108
|
+
0, 0, 1
|
|
1109
|
+
]))
|
|
1110
|
+
|
|
1111
|
+
return this
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* @param {number} x
|
|
1116
|
+
* @param {number} y
|
|
1117
|
+
*/
|
|
1118
|
+
scale (x, y) {
|
|
1119
|
+
this.multiply(new Transform.Matrix([
|
|
1120
|
+
x, 0, 0,
|
|
1121
|
+
0, y, 0,
|
|
1122
|
+
0, 0, 1
|
|
1123
|
+
]))
|
|
1124
|
+
|
|
1125
|
+
return this
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* @param {number} a - the angle or rotation in radians
|
|
1130
|
+
*/
|
|
1131
|
+
rotate (a) {
|
|
1132
|
+
const c = Math.cos(a); const s = Math.sin(a)
|
|
1133
|
+
this.multiply(new Transform.Matrix([
|
|
1134
|
+
c, s, 0,
|
|
1135
|
+
-s, c, 0,
|
|
1136
|
+
0, 0, 1
|
|
1137
|
+
]))
|
|
1138
|
+
|
|
1139
|
+
return this
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* The identity matrix
|
|
1144
|
+
*/
|
|
1145
|
+
Transform.Matrix.IDENTITY = new Transform.Matrix()
|
|
1146
|
+
const TMP_MATRIX = new Transform.Matrix()
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Preserves an ellipse of the layer and clears the rest
|
|
1150
|
+
* @todo Parent layer mask effects will make more complex masks easier
|
|
1151
|
+
*/
|
|
1152
|
+
export class EllipticalMask extends Base {
|
|
1153
|
+
constructor (x, y, radiusX, radiusY, rotation = 0, startAngle = 0, endAngle = 2 * Math.PI, anticlockwise = false) {
|
|
1154
|
+
super()
|
|
1155
|
+
this.x = x
|
|
1156
|
+
this.y = y
|
|
1157
|
+
this.radiusX = radiusX
|
|
1158
|
+
this.radiusY = radiusY
|
|
1159
|
+
this.rotation = rotation
|
|
1160
|
+
this.startAngle = startAngle
|
|
1161
|
+
this.endAngle = endAngle
|
|
1162
|
+
this.anticlockwise = anticlockwise
|
|
1163
|
+
// for saving image data before clearing
|
|
1164
|
+
this._tmpCanvas = document.createElement('canvas')
|
|
1165
|
+
this._tmpCtx = this._tmpCanvas.getContext('2d')
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
apply (target, reltime) {
|
|
1169
|
+
const ctx = target.cctx; const canvas = target.canvas
|
|
1170
|
+
const x = val(this.x, target, reltime); const y = val(this.y, target, reltime)
|
|
1171
|
+
const radiusX = val(this.radiusX, target, reltime); const radiusY = val(this.radiusY, target, reltime)
|
|
1172
|
+
const rotation = val(this.rotation, target, reltime)
|
|
1173
|
+
const startAngle = val(this.startAngle, target, reltime); const endAngle = val(this.endAngle, target, reltime)
|
|
1174
|
+
const anticlockwise = val(this.anticlockwise, target, reltime)
|
|
1175
|
+
this._tmpCanvas.width = target.canvas.width
|
|
1176
|
+
this._tmpCanvas.height = target.canvas.height
|
|
1177
|
+
this._tmpCtx.drawImage(canvas, 0, 0)
|
|
1178
|
+
|
|
1179
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
1180
|
+
ctx.save() // idk how to preserve clipping state without save/restore
|
|
1181
|
+
// create elliptical path and clip
|
|
1182
|
+
ctx.beginPath()
|
|
1183
|
+
ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
1184
|
+
ctx.closePath()
|
|
1185
|
+
ctx.clip()
|
|
1186
|
+
// render image with clipping state
|
|
1187
|
+
ctx.drawImage(this._tmpCanvas, 0, 0)
|
|
1188
|
+
ctx.restore()
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
</code></pre>
|
|
1192
|
+
</article>
|
|
1193
|
+
</section>
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
</div>
|
|
1201
|
+
|
|
1202
|
+
<br class="clear">
|
|
1203
|
+
|
|
1204
|
+
<footer>
|
|
1205
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a> on Sun Oct 13 2019 15:38:43 GMT-0400 (Eastern Daylight Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
1206
|
+
</footer>
|
|
1207
|
+
|
|
1208
|
+
<script>prettyPrint();</script>
|
|
1209
|
+
<script src="scripts/polyfill.js"></script>
|
|
1210
|
+
<script src="scripts/linenumber.js"></script>
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
</body>
|
|
1215
|
+
</html>
|