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.
Files changed (103) hide show
  1. package/.env +2 -0
  2. package/.github/workflows/nodejs.yml +27 -0
  3. package/CHANGELOG.md +109 -0
  4. package/CODE_OF_CONDUCT.md +77 -0
  5. package/CONTRIBUTING.md +155 -0
  6. package/LICENSE +674 -0
  7. package/README.md +57 -0
  8. package/dist/etro.js +3397 -0
  9. package/docs/effect.js.html +1215 -0
  10. package/docs/event.js.html +145 -0
  11. package/docs/index.html +81 -0
  12. package/docs/index.js.html +92 -0
  13. package/docs/layer.js.html +888 -0
  14. package/docs/module-effect-GaussianBlurComponent.html +345 -0
  15. package/docs/module-effect.Brightness.html +339 -0
  16. package/docs/module-effect.Channels.html +319 -0
  17. package/docs/module-effect.ChromaKey.html +611 -0
  18. package/docs/module-effect.Contrast.html +339 -0
  19. package/docs/module-effect.EllipticalMask.html +200 -0
  20. package/docs/module-effect.GaussianBlur.html +202 -0
  21. package/docs/module-effect.GaussianBlurHorizontal.html +242 -0
  22. package/docs/module-effect.GaussianBlurVertical.html +242 -0
  23. package/docs/module-effect.Pixelate.html +330 -0
  24. package/docs/module-effect.Shader.html +1227 -0
  25. package/docs/module-effect.Stack.html +406 -0
  26. package/docs/module-effect.Transform.Matrix.html +193 -0
  27. package/docs/module-effect.Transform.html +1174 -0
  28. package/docs/module-effect.html +148 -0
  29. package/docs/module-event.html +473 -0
  30. package/docs/module-index.html +186 -0
  31. package/docs/module-layer-Media.html +1116 -0
  32. package/docs/module-layer-MediaMixin.html +164 -0
  33. package/docs/module-layer.Audio.html +1188 -0
  34. package/docs/module-layer.Base.html +629 -0
  35. package/docs/module-layer.Image.html +1421 -0
  36. package/docs/module-layer.Text.html +1731 -0
  37. package/docs/module-layer.Video.html +1938 -0
  38. package/docs/module-layer.Visual.html +1698 -0
  39. package/docs/module-layer.html +137 -0
  40. package/docs/module-movie.html +3118 -0
  41. package/docs/module-util.Color.html +702 -0
  42. package/docs/module-util.Font.html +395 -0
  43. package/docs/module-util.html +845 -0
  44. package/docs/movie.js.html +689 -0
  45. package/docs/scripts/collapse.js +20 -0
  46. package/docs/scripts/linenumber.js +25 -0
  47. package/docs/scripts/nav.js +12 -0
  48. package/docs/scripts/polyfill.js +4 -0
  49. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  50. package/docs/scripts/prettify/lang-css.js +2 -0
  51. package/docs/scripts/prettify/prettify.js +28 -0
  52. package/docs/scripts/search.js +83 -0
  53. package/docs/styles/jsdoc.css +671 -0
  54. package/docs/styles/prettify.css +79 -0
  55. package/docs/util.js.html +503 -0
  56. package/eslint.conf.js +28 -0
  57. package/eslint.test-conf.js +4 -0
  58. package/examples/application/readme-screenshot.html +86 -0
  59. package/examples/application/video-player.html +131 -0
  60. package/examples/application/webcam.html +28 -0
  61. package/examples/introduction/audio.html +52 -0
  62. package/examples/introduction/effects.html +56 -0
  63. package/examples/introduction/export.html +70 -0
  64. package/examples/introduction/functions.html +35 -0
  65. package/examples/introduction/hello-world1.html +33 -0
  66. package/examples/introduction/hello-world2.html +32 -0
  67. package/examples/introduction/keyframes.html +67 -0
  68. package/examples/introduction/media.html +55 -0
  69. package/examples/introduction/text.html +27 -0
  70. package/jsdoc.conf.json +3 -0
  71. package/karma.conf.js +60 -0
  72. package/package.json +63 -0
  73. package/private-todo.txt +70 -0
  74. package/rename-file.sh +18 -0
  75. package/rename-versions.sh +14 -0
  76. package/rename.sh +22 -0
  77. package/rollup.config.js +31 -0
  78. package/screenshots/2019-08-17_0.png +0 -0
  79. package/scripts/gen-effect-samples.html +99 -0
  80. package/scripts/save-effect-samples.js +43 -0
  81. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  82. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  83. package/spec/assets/effect/original.png +0 -0
  84. package/spec/assets/effect/pixelate.png +0 -0
  85. package/spec/assets/effect/transform/multiply.png +0 -0
  86. package/spec/assets/effect/transform/rotate.png +0 -0
  87. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  88. package/spec/assets/effect/transform/scale.png +0 -0
  89. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  90. package/spec/assets/effect/transform/translate.png +0 -0
  91. package/spec/assets/layer/audio.wav +0 -0
  92. package/spec/assets/layer/image.jpg +0 -0
  93. package/spec/effect.spec.js +352 -0
  94. package/spec/event.spec.js +25 -0
  95. package/spec/layer.spec.js +128 -0
  96. package/spec/movie.spec.js +154 -0
  97. package/spec/util.spec.js +285 -0
  98. package/src/effect.js +1265 -0
  99. package/src/event.js +78 -0
  100. package/src/index.js +23 -0
  101. package/src/layer.js +875 -0
  102. package/src/movie.js +636 -0
  103. 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
+ * &lt;em>Note: At this time, simply use the &lt;code>actx&lt;/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.&lt;/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 &lt; 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: &lt;code>1f&lt;/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) &amp;&amp; (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) &amp;&amp; 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 &amp;&amp; isPowerOf2(w)) &amp;&amp; (h &amp;&amp; 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 &amp;&amp; (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 &lt;code>Shader&lt;/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 &amp; 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 &lt;code>smoothingSharpness&lt;/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 &lt;= thresh &amp;&amp; dist.g &lt;= thresh &amp;&amp; dist.b &lt;= 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 &lt; 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 &lt; pascal.length; i++) {
790
+ sum += pascal[i]
791
+ }
792
+ for (let i = 0; i &lt; pascal.length; i++) {
793
+ pascal[i] /= sum
794
+ }
795
+ return pascal
796
+ }
797
+ GaussianBlur.genPascalRow = index => {
798
+ if (index &lt; 0) {
799
+ throw new Error(`Invalid index ${index}`)
800
+ }
801
+ let currRow = [1]
802
+ for (let i = 1; i &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt;code>this&lt;/code> with another matrix &lt;code>other&lt;/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&amp;seqNum=4
1084
+ for (let x = 0; x &lt; 3; x++) {
1085
+ for (let y = 0; y &lt; 3; y++) {
1086
+ let sum = 0
1087
+ for (let i = 0; i &lt; 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 &lt; 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>